# -*- coding: utf-8 -*-
"""
千人千店访销助手API接口
====================

数据库表结构说明：
- users: 用户表 (用户ID, 账号, 密码, 姓名, 角色, 头像, 认证令牌, 创建时间, 更新时间)
- customers: 客户表 (客户ID, 客户门店名称, 是否为新客户, 归属类型, 所属区域, 详细地址, 联系人姓名, 联系方式, 客户状态, 签约开始月份, 签约结束月份, 属性分类, 类型分类, 面积, 会员数量, 教练数量, 已添加教练数量, 教练社交活动, 试次活动, 活动植入, 赞助赠送, 易拉宝投放, 展柜投放, 教练社交活动人数, 试次活动次数, 活动植入次数, 赞助赠送详情, 易拉宝投放数量, 展柜投放数量, 备注信息, 计划内容, 打卡照片, 创建人, 创建人ID, 创建人头像, 创建时间, 更新时间)
- schedules: 日程表 (日程ID, 客户ID, 客户姓名, 日程标题, 日程描述, 开始时间, 结束时间, 日程状态, 创建时间, 更新时间)
- store_schedule_plans: 门店计划表 (门店计划ID, 门店ID, 门店名称, 门店地址, 门店状态, 访问状态, 用户ID, 计划日期, 门店类型, 区域, 备注, 创建时间, 更新时间)
- visit_records: 回访记录表 (回访记录ID, 门店ID, 客户门店名称, 是否为新客户, 归属类型, 所属区域, 详细地址, 联系人姓名, 联系方式, 客户状态, 签约开始月份, 签约结束月份, 属性分类, 类型分类, 面积, 会员数量, 教练数量, 已添加教练数量, 教练社交活动, 试次活动, 活动植入, 赞助赠送, 易拉宝投放, 展柜投放, 教练社交活动人数, 试次活动次数, 活动植入次数, 赞助赠送详情, 易拉宝投放数量, 展柜投放数量, 备注信息, 计划内容, 打卡照片, 创建人, 创建人ID, 创建人头像, 创建时间)
- user_visit_records: 用户访问记录表 (访问记录ID, 用户ID, 计划日期, 创建时间)
- notices: 公告表 (公告ID, 用户ID, 公告内容, 创建时间, 更新时间)
- file_uploads: 文件上传表 (文件ID, 文件唯一标识, 文件名, 文件URL, 上传时间)
- department_users: 部门用户关系表 (关系ID, 上级用户ID, 下属用户ID, 创建时间)
"""
from pymysql.cursors import DictCursor as _DictCursorAlias  # ensure DictCursor available here
import uuid
import traceback
from datetime import datetime, date, timedelta
from typing import Optional, List, Dict, Any, Union
import io
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
import pymysql
from pymysql.cursors import DictCursor
import uvicorn
from fastapi import FastAPI, Query, File, UploadFile, HTTPException, Header, Depends, Request, Body
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Optional
import base64
import hashlib
import hmac
import json
import time
import os
from datetime import datetime, timedelta
from alibabacloud_credentials.client import Client as CredClient
from alibabacloud_credentials.models import Config as CredConfig
try:
    from alibabacloud_tea_openapi.models import Config as TeaConfig
except ImportError:
    # 如果导入失败，尝试其他导入方式
    try:
        from alibabacloud_tea_openapi import models
        TeaConfig = models.Config
    except ImportError:
        raise ImportError("无法导入 alibabacloud_tea_openapi.models.Config，请检查是否安装了 alibabacloud-tea-openapi 包")
from alibabacloud_sts20150401.client import Client as Sts20150401Client
from alibabacloud_sts20150401 import models as sts_20150401_models

# FastAPI应用实例
app = FastAPI()

# CORS跨域配置
origins = ["*"]  # 允许的来源，可以是具体的域名列表，例如 ["http://localhost", "https://example.com"]
methods = ["GET", "POST", "PUT", "DELETE"]  # 允许的 HTTP 方法
allow_credentials = True  # 是否允许发送身份验证信息（cookies）到服务器

# 添加CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=allow_credentials,
    allow_methods=methods,
    allow_headers=["*"],
)


# 统一的用户ID处理函数
async def get_user_id(userid: Optional[str] = Header(None, description="用户ID")):
    """
    从请求头获取用户ID
    所有接口都需要在请求头中传递userid
    """
    if not userid:
        raise HTTPException(status_code=400, detail="缺少用户ID请求头")

    try:
        user_id = int(userid)
        return user_id
    except ValueError:
        raise HTTPException(status_code=400, detail="用户ID格式错误")


# 数据库连接配置
# 连接到阿里云RDS MySQL数据库
DB_CONFIG = {
    'host': 'rm-2ze6q103m284te7f7eo.mysql.rds.aliyuncs.com',  # 数据库主机地址
    'port': 3306,  # 数据库端口
    'user': "root",  # 数据库用户名
    'password': 'Ds2024@()833429',  # 数据库密码
    'database': "qrqd"  # 数据库名称
}

# 系统配置常量
DEFAULT_CREATOR = 'system'  # 默认创建人，用于客户创建时的created_by字段


# ==================== 辅助函数 ====================

def build_region_like_condition(prefix: str, field_name: str = "region") -> tuple:
    """
    构建region字段的4种格式匹配条件（增强健壮性）
    支持以下4种格式：
    1. {prefix}/xxx%（无"省"字，斜杠分隔）
    2. {prefix} xxx%（无"省"字，空格分隔）
    3. {prefix}省/xxx%（有"省"字，斜杠分隔）
    4. {prefix}省 xxx%（有"省"字，空格分隔）
    
    Args:
        prefix: 前缀字符串（如"四川"、"四川/成都市"等）
        field_name: 字段名，默认为"region"
    
    Returns:
        tuple: (SQL条件字符串, 参数列表)
    """
    condition = f"({field_name} LIKE %s OR {field_name} LIKE %s OR {field_name} LIKE %s OR {field_name} LIKE %s)"
    params = [
        f"{prefix}%",           # 四川%
        f"{prefix}/%",          # 四川/%
        f"{prefix}省%",         # 四川省%
        f"{prefix}省 %"         # 四川省 %
    ]
    return condition, params


def build_city_like_condition(province: str, city: str, field_name: str = "city") -> tuple:
    """
    构建city字段的4种格式匹配条件（增强健壮性）
    支持以下4种格式：
    1. {province}/{city}%（无"省"字，斜杠分隔）
    2. {province} {city}%（无"省"字，空格分隔）
    3. {province}省/{city}%（有"省"字，斜杠分隔）
    4. {province}省 {city}%（有"省"字，空格分隔）
    
    Args:
        province: 省份名称（如"四川"）
        city: 城市名称（如"成都市"）
        field_name: 字段名，默认为"city"
    
    Returns:
        tuple: (SQL条件字符串, 参数列表)
    """
    condition = f"({field_name} LIKE %s OR {field_name} LIKE %s OR {field_name} LIKE %s OR {field_name} LIKE %s)"
    params = [
        f"{province}/{city}%",      # 四川/成都市%
        f"{province} {city}%",      # 四川 成都市%
        f"{province}省/{city}%",    # 四川省/成都市%
        f"{province}省 {city}%"     # 四川省 成都市%
    ]
    return condition, params


def build_region_contains_condition(keyword: str, field_name: str = "region") -> tuple:
    """
    构建region字段的包含匹配条件（用于城市、区县等包含匹配）
    由于不知道具体格式，使用包含匹配 %{keyword}%
    
    Args:
        keyword: 关键词（如"成都市"、"锦江区"）
        field_name: 字段名，默认为"region"
    
    Returns:
        tuple: (SQL条件字符串, 参数列表)
    """
    condition = f"{field_name} LIKE %s"
    params = [f"%{keyword}%"]
    return condition, params


def normalize_region_to_list(region: Union[str, List[str], None]) -> List[str]:
    """
    将region字段标准化为数组格式
    支持字符串、数组、None等格式
    
    Args:
        region: 区域值，可以是字符串、数组或None
    
    Returns:
        List[str]: 标准化后的区域数组
    """
    if region is None:
        return []
    if isinstance(region, str):
        # 如果是字符串，转换为数组
        if not region.strip():
            return []
        return [region.strip()]
    if isinstance(region, list):
        # 如果是数组，去重并去除空值
        return sorted(list(set([r.strip() for r in region if r and r.strip()])))
    return []


def format_region_for_storage(region: Union[str, List[str], None]) -> str:
    """
    将region字段格式化为JSON字符串用于数据库存储
    支持字符串、数组格式，统一转换为JSON字符串
    
    Args:
        region: 区域值，可以是字符串、数组或None
    
    Returns:
        str: JSON格式的字符串，如 '["北京/北京市/全部", "上海/上海市/全部"]' 或 '["北京/北京市/全部"]'
    """
    import json
    region_list = normalize_region_to_list(region)
    if not region_list:
        return '[]'
    return json.dumps(region_list, ensure_ascii=False)


def parse_region_from_storage(region_value: Any) -> List[str]:
    """
    从数据库读取region字段并解析为数组格式
    支持JSON字符串、普通字符串、数组等格式
    
    Args:
        region_value: 从数据库读取的region值
    
    Returns:
        List[str]: 解析后的区域数组
    """
    import json
    if region_value is None:
        return []
    if isinstance(region_value, list):
        # 如果已经是数组，直接返回
        return normalize_region_to_list(region_value)
    if isinstance(region_value, str):
        if not region_value.strip():
            return []
        # 尝试解析为JSON
        try:
            parsed = json.loads(region_value)
            if isinstance(parsed, list):
                return normalize_region_to_list(parsed)
            # 如果不是数组，当作普通字符串处理
            return normalize_region_to_list(parsed)
        except (json.JSONDecodeError, TypeError):
            # 如果不是JSON格式，当作普通字符串处理
            return normalize_region_to_list(region_value)
    return []

# 地图API配置（百度地图）
MAP_API_CONFIG = {
    'ak': 'eTg5SBFOio6J0N2qciBKbNg2NDhfxgY4',  # 百度地图API密钥
    'base_url': 'http://api.map.baidu.com/geocoding/v3/'  # 百度地图地理编码API地址
}


# ==================== Pydantic数据模型定义 ====================

class LoginRequest(BaseModel):
    """用户登录请求模型"""
    account: str  # 账号 (对应users表的account字段)
    password: str  # 密码 (对应users表的password字段)


class UserResponse(BaseModel):
    """用户信息响应模型"""
    id: str  # 用户ID (对应users表的id字段)
    account: str  # 账号 (对应users表的account字段)
    password: str  # 密码 (对应users表的password字段)
    name: str  # 姓名 (对应users表的name字段)
    role: str  # 角色 (对应users表的role字段)
    avatar: str  # 头像 (对应users表的avatar字段)
    token: str  # 认证令牌 (对应users表的token字段)


class ApiResponse(BaseModel):
    """API统一响应模型"""
    data: Any  # 响应数据
    message: str  # 响应消息
    status: int  # 响应状态码


class ChangePasswordRequest(BaseModel):
    """修改密码请求模型"""
    oldPassword: str  # 原密码
    newPassword: str  # 新密码


class AuditRequest(BaseModel):
    """审核请求模型"""
    auditStatus: str  # 审核状态
    remark: Optional[str] = None  # 审核意见


class ImageUrlRequest(BaseModel):
    """图片URL请求模型"""
    imageUrl: str  # 图片URL地址
    fileName: Optional[str] = None  # 文件名（可选）


class ProfileUpdateRequest(BaseModel):
    """更新用户资料请求模型"""
    name: Optional[str] = None  # 姓名 (对应users表的name字段)
    avatar: Optional[str] = None  # 头像 (对应users表的avatar字段)


class ScheduleRequest(BaseModel):
    customerId: Optional[int] = None
    title: str
    description: Optional[str] = None
    startTime: Optional[str] = None
    endTime: Optional[str] = None
    date: Optional[str] = None
    time: Optional[str] = None
    status: Optional[str] = "pending"
    notes: Optional[str] = None


class CustomerRequest(BaseModel):
    name: str
    isNew: Optional[bool] = False
    ownership: Optional[str] = "store"
    region: Optional[str] = None
    address: Optional[str] = None
    contactPerson: Optional[str] = None
    contactMethod: Optional[str] = None
    status: Optional[str] = "未建联"
    contractPeriod: Optional[Dict] = None
    attribute: Optional[str] = None
    type: Optional[str] = None
    area: Optional[str] = None
    memberCount: Optional[int] = 0
    coachCount: Optional[int] = 0
    addedCoachCount: Optional[int] = 0
    storeActions: Optional[Dict] = None
    actionValues: Optional[Dict] = None
    remarks: Optional[str] = None
    plan: Optional[str] = None
    checkInPhoto: Optional[str] = None


class VisitRecordRequest(BaseModel):
    """回访记录请求模型 - 对应visit_records表结构"""
    id: Optional[int] = None  # 回访记录ID (对应visit_records表的id字段)
    name: str  # 客户门店名称 (对应visit_records表的name字段)
    isNew: Optional[bool] = False  # 是否为新客户 (对应visit_records表的is_new字段)
    ownership: Optional[str] = "store"  # 归属类型 (对应visit_records表的ownership字段)
    region: Optional[str] = None  # 所属区域 (对应visit_records表的region字段)
    address: Optional[str] = None  # 详细地址 (对应visit_records表的address字段)
    contactPerson: Optional[str] = None  # 联系人姓名 (对应visit_records表的contact_person字段)
    contactMethod: Optional[str] = None  # 联系方式 (对应visit_records表的contact_method字段)
    contactWay: Optional[str] = None  # 联系方式（前端常用字段名，如果传了contactWay且没传contactMethod，则使用contactWay的值）
    status: Optional[str] = "未建联"  # 客户状态 (对应visit_records表的status字段)
    contractPeriod: Optional[Dict] = None  # 合同期限 (对应visit_records表的contract_start_month和contract_end_month字段)
    attribute: Optional[str] = None  # 属性分类 (对应visit_records表的attribute字段)
    type: Optional[str] = None  # 类型分类 (对应visit_records表的type字段)
    area: Optional[str] = None  # 面积 (对应visit_records表的area字段)
    memberCount: Optional[int] = 0  # 会员数量 (对应visit_records表的member_count字段)
    coachCount: Optional[int] = 0  # 教练数量 (对应visit_records表的coach_count字段)
    addedCoachCount: Optional[int] = 0  # 已添加教练数量 (对应visit_records表的added_coach_count字段)
    storeActions: Optional[Dict] = None  # 门店活动 (对应visit_records表的coach_social, trial_activity等字段)
    actionValues: Optional[Dict] = None  # 活动数值 (对应visit_records表的coach_social_count, trial_activity_count等字段)
    remarks: Optional[str] = None  # 备注信息 (对应visit_records表的remarks字段)
    plan: Optional[str] = None  # 计划内容 (对应visit_records表的plan字段)
    checkInPhoto: Optional[str] = None  # 打卡照片 (对应visit_records表的check_in_photo字段)
    createdBy: Optional[str] = None  # 创建人 (对应visit_records表的created_by字段)
    createdId: Optional[int] = None  # 创建人ID (已弃用，保留以兼容前端，实际使用 created_by 字段)
    avatar: Optional[str] = None  # 创建人头像 (对应visit_records表的avatar字段)


# ==================== 新增客户API相关模型 ====================

class CustomerListRequest(BaseModel):
    """客户列表请求模型"""
    region: Optional[str] = None  # 区域筛选
    attribute: Optional[str] = None  # 属性分类筛选
    type: Optional[str] = None  # 类型分类筛选
    status: Optional[str] = None  # 客户状态筛选
    searchQuery: Optional[str] = None  # 搜索关键词
    ownership: Optional[str] = None  # 归属类型筛选
    page: Optional[int] = 1  # 页码
    pageSize: Optional[int] = 10  # 每页数量


class CustomerCreateRequest(BaseModel):
    """创建客户请求模型 - 对应customers表结构"""
    name: str  # 客户门店名称 (对应customers表的name字段)
    ownership: Optional[str] = "store"  # 归属类型 (对应customers表的ownership字段)
    region: Optional[str] = None  # 所属区域 (对应customers表的region字段)
    address: Optional[str] = None  # 详细地址 (对应customers表的address字段)
    contactPerson: Optional[str] = None  # 联系人姓名 (对应customers表的contact_person字段)
    contactMethod: Optional[str] = None  # 联系方式 (对应customers表的contact_method字段)
    status: Optional[str] = "未建联"  # 客户状态 (对应customers表的status字段)
    contractPeriod: Optional[Dict] = None  # 合同期限 (对应customers表的contract_start_month和contract_end_month字段)
    attribute: Optional[str] = None  # 属性分类 (对应customers表的attribute字段)
    type: Optional[str] = None  # 类型分类 (对应customers表的type字段)
    area: Optional[str] = None  # 面积 (对应customers表的area字段)
    memberCount: Optional[int] = 0  # 会员数量 (对应customers表的member_count字段)
    coachCount: Optional[int] = 0  # 教练数量 (对应customers表的coach_count字段)
    addedCoachCount: Optional[int] = 0  # 已添加教练数量 (对应customers表的added_coach_count字段)
    storeActions: Optional[Dict] = None  # 门店活动 (对应customers表的coach_social, trial_activity等字段)
    actionValues: Optional[Dict] = None  # 活动数值 (对应customers表的coach_social_count, trial_activity_count等字段)
    remarks: Optional[str] = None  # 备注信息 (对应customers表的remarks字段)
    plan: Optional[str] = None  # 计划内容 (对应customers表的plan字段)
    checkInPhoto: Optional[str] = None  # 打卡照片 (对应customers表的check_in_photo字段)
    createdBy: Optional[str] = None  # 创建人 (对应customers表的created_by字段)
    owner: Optional[int] = None  # 所有者ID (对应customers表的owner字段)
    lat: Optional[float] = None  # 纬度 (对应customers表的lat字段)
    lng: Optional[float] = None  # 经度 (对应customers表的lng字段)


class CustomerUpdateRequest(BaseModel):
    """更新客户请求模型 - 对应customers表结构"""
    name: Optional[str] = None  # 客户门店名称 (对应customers表的name字段)
    ownership: Optional[str] = None  # 归属类型 (对应customers表的ownership字段)
    region: Optional[str] = None  # 所属区域 (对应customers表的region字段)
    address: Optional[str] = None  # 详细地址 (对应customers表的address字段)
    contactPerson: Optional[str] = None  # 联系人姓名 (对应customers表的contact_person字段)
    contactMethod: Optional[str] = None  # 联系方式 (对应customers表的contact_method字段)
    status: Optional[str] = None  # 客户状态 (对应customers表的status字段)
    contractPeriod: Optional[Dict] = None  # 合同期限 (对应customers表的contract_start_month和contract_end_month字段)
    attribute: Optional[str] = None  # 属性分类 (对应customers表的attribute字段)
    type: Optional[str] = None  # 类型分类 (对应customers表的type字段)
    area: Optional[str] = None  # 面积 (对应customers表的area字段)
    memberCount: Optional[int] = None  # 会员数量 (对应customers表的member_count字段)
    coachCount: Optional[int] = None  # 教练数量 (对应customers表的coach_count字段)
    addedCoachCount: Optional[int] = None  # 已添加教练数量 (对应customers表的added_coach_count字段)
    storeActions: Optional[Dict] = None  # 门店活动 (对应customers表的coach_social, trial_activity等字段)
    actionValues: Optional[Dict] = None  # 活动数值 (对应customers表的coach_social_count, trial_activity_count等字段)
    remarks: Optional[str] = None  # 备注信息 (对应customers表的remarks字段)
    plan: Optional[str] = None  # 计划内容 (对应customers表的plan字段)
    checkInPhoto: Optional[str] = None  # 打卡照片 (对应customers表的check_in_photo字段)
    createdBy: Optional[str] = None  # 创建人 (对应customers表的created_by字段)
    owner: Optional[Union[int, str]] = None  # 所有者ID (对应customers表的owner字段，支持空字符串表示null)
    lat: Optional[float] = None  # 纬度 (对应customers表的lat字段)
    lng: Optional[float] = None  # 经度 (对应customers表的lng字段)


class ScheduleCreateRequest(BaseModel):
    """创建日程请求模型 - 对应schedules表结构"""
    customerId: Optional[int] = None  # 客户ID (对应schedules表的customer_id字段)
    title: str  # 日程标题 (对应schedules表的title字段)
    description: Optional[str] = None  # 日程描述 (对应schedules表的description字段)
    startTime: Optional[str] = None  # 开始时间 (对应schedules表的start_time字段)
    endTime: Optional[str] = None  # 结束时间 (对应schedules表的end_time字段)
    date: Optional[str] = None  # 日期
    time: Optional[str] = None  # 时间
    status: Optional[str] = "pending"  # 日程状态 (对应schedules表的status字段)
    notes: Optional[str] = None  # 备注


class ScheduleUpdateRequest(BaseModel):
    """更新日程请求模型 - 对应schedules表结构"""
    customerId: Optional[int] = None  # 客户ID (对应schedules表的customer_id字段)
    title: Optional[str] = None  # 日程标题 (对应schedules表的title字段)
    description: Optional[str] = None  # 日程描述 (对应schedules表的description字段)
    startTime: Optional[str] = None  # 开始时间 (对应schedules表的start_time字段)
    endTime: Optional[str] = None  # 结束时间 (对应schedules表的end_time字段)
    status: Optional[str] = None  # 日程状态 (对应schedules表的status字段)


class CustomScheduleRequest(BaseModel):
    """自定义日程请求模型 - 对应store_schedule_plans表结构"""
    id: Optional[int] = None  # 门店ID (对应store_schedule_plans表的store_id字段)
    name: str  # 门店名称 (对应store_schedule_plans表的name字段)
    address: Optional[str] = None  # 门店地址 (对应store_schedule_plans表的address字段)
    status: Optional[str] = "自定义"  # 门店状态 (对应store_schedule_plans表的status字段)
    visitedstatus: Optional[bool] = False  # 访问状态
    userId: int  # 用户ID
    scheduleDate: str  # 计划日期
    type: Optional[int] = None  # 门店类型
    region: Optional[str] = None  # 区域
    remark: Optional[str] = None  # 备注


class FeedbackRequest(BaseModel):
    """反馈请求模型"""
    content: str  # 反馈内容
    type: str  # 反馈类型


class AvatarRequest(BaseModel):
    """头像请求模型"""
    avatar: str  # 头像文件名


class PaginatedResponse(BaseModel):
    """分页响应模型"""
    data: List[Any]  # 数据列表
    message: str  # 响应消息
    status: int  # 响应状态码
    total: int  # 总数量
    page: int  # 当前页码
    pageSize: int  # 每页数量


class WrappedApiResponse(BaseModel):
    """包装后的API响应模型 - 适配前端拦截器"""
    data: Any  # 包装后的数据，可以是dict、list或其他类型
    message: str  # 响应消息
    status: int  # 响应状态码


def create_wrapped_response(data, message="操作成功", status=200):
    """创建包装后的响应 - 适配前端拦截器"""
    return WrappedApiResponse(
        data={"data": data},
        message=message,
        status=status
    )


def create_paginated_response(data, total, page, pageSize, message="获取成功", status=200):
    """创建分页包装后的响应 - 适配前端拦截器"""
    return WrappedApiResponse(
        data={
            "data": data,
            "total": total,
            "page": page,
            "pageSize": pageSize
        },
        message=message,
        status=status
    )


# 数据库连接函数
# ==================== 数据库连接函数 ====================

def get_db_connection():
    """
    获取数据库连接
    连接到阿里云RDS MySQL数据库
    返回: pymysql连接对象
    异常: 连接失败时抛出HTTPException
    """
    try:
        connection = pymysql.connect(**DB_CONFIG)
        return connection
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"数据库连接失败: {str(e)}")


def get_lat_lng_by_address(address: str) -> Optional[dict]:
    """
    根据地址获取经纬度（使用百度地图API）
    :param address: 需要查询的地址（如"北京市海淀区上地十街10号"）
    :return: 返回经纬度字典，格式为 {'lat': 纬度, 'lng': 经度}，失败返回None
    """
    if not address or not address.strip():
        return None

    try:
        import requests
        url = MAP_API_CONFIG['base_url']
        params = {
            "address": address.strip(),
            "output": "json",
            "ak": MAP_API_CONFIG['ak']
        }

        response = requests.get(url, params=params, timeout=5)
        result = response.json()

        if result.get("status") == 0:  # status=0 表示请求成功
            location = result["result"]["location"]
            return {
                "lat": float(location["lat"]),
                "lng": float(location["lng"])
            }
        else:
            # 请求失败，记录日志但不抛出异常
            print(f"地图API请求失败，地址：{address}，错误码：{result.get('status')}，错误信息：{result.get('message')}")
            return None
    except Exception as e:
        # 请求异常，记录日志但不抛出异常，避免影响主流程
        print(f"地图API请求异常，地址：{address}，异常：{str(e)}")
        return None


def generate_token():
    """
    生成唯一token
    返回: UUID字符串
    """
    return f"token-{uuid.uuid4().hex}"


# ==================== 认证相关接口 ====================

# 1.1 用户登录
@app.post("/auth/login", response_model=WrappedApiResponse)
async def login(request: LoginRequest, user_id: int = Depends(get_user_id)):
    """
    用户登录接口
    文档来源: 千人千店api接口.md - 1.1 用户登录
    操作表: users
    功能: 验证用户账号密码，返回用户信息
    参数: account(账号), password(密码)
    返回: 用户完整信息
    """
    try:
        # 获取数据库连接
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 查询用户 - 从users表验证账号密码
        cursor.execute("SELECT * FROM users WHERE account = %s AND password = %s",
                       (request.account, request.password))
        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=401, detail="用户名或密码错误")

        # 查询用户的组织地域信息
        # users.department 对应 organizations.name
        # department字段可能是多个组织，用逗号分隔（如 "销售部,市场部"）
        organization_region = ['all']  # 默认为 ['all']，表示全部，不过滤
        if user.get('department'):
            # 将department按逗号分割，获取所有组织名称
            department_str = user['department'].strip()
            if department_str:
                org_names = [name.strip() for name in department_str.split(',') if name.strip()]
                
                if org_names:
                    # 依次查询每个组织名称对应的region
                    region_list = []
                    for org_name in org_names:
                        cursor.execute("""
                            SELECT region 
                            FROM organizations 
                            WHERE name = %s AND region IS NOT NULL AND region != ''
                        """, (org_name,))
                        org_result = cursor.fetchone()
                        
                        if org_result:
                            region_value = org_result.get('region')
                            if region_value:
                                # 解析region（可能是JSON数组或字符串）
                                parsed_regions = parse_region_from_storage(region_value)
                                for region_item in parsed_regions:
                                    if region_item:
                                        # 删除"省"字
                                        region_item = region_item.replace('省', '')
                                        # 确保格式为 "省/市/all"，如果只有省/市，则添加"/all"
                                        if region_item and '/' in region_item:
                                            parts = region_item.split('/')
                                            if len(parts) == 2:
                                                # 如果是 "北京/北京市" 格式，转换为 "北京/北京市/all"
                                                region_item = f"{region_item}/all"
                                            elif len(parts) == 3 and parts[2] != 'all':
                                                # 如果是 "北京/北京市/东城区" 格式，转换为 "北京/北京市/all"
                                                region_item = f"{parts[0]}/{parts[1]}/all"
                                        if region_item:
                                            region_list.append(region_item)
                    
                    # 去重并排序
                    region_list = sorted(list(set(region_list)))
                    if region_list:
                        # 如果有区域，返回区域数组（不包含 'all'）
                        organization_region = region_list
                    # 如果没有区域，保持 ['all']

        # 返回用户信息（不需要token）
        user_data = {
            "id": str(user['id']),  # 用户ID
            "userid": str(user['id']),  # 用户ID（用于前端存储）
            "account": user['account'],  # 账号
            "password": user['password'],  # 密码
            "name": user['name'],  # 姓名
            "role": user['role'],  # 角色
            "position": user['role'],  # 职位（等于role的值）
            "avatar": user['avatar'] or "",  # 头像
            "token": user['token'] or "",  # 认证令牌
            "organization_region": organization_region  # 组织地域（数组格式，如 ['all', '北京/北京市/全部']）
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="登录成功"
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"登录失败: {str(e)}")


# 1.2 用户登出
@app.post("/auth/logout", response_model=WrappedApiResponse)
async def logout(user_id: int = Depends(get_user_id)):
    """
    用户登出接口
    文档来源: 千人千店api接口.md - 1.2 用户登出
    操作表: 无
    功能: 用户登出，返回成功消息
    参数: 无
    返回: 登出成功消息
    """
    return create_wrapped_response(
        data=None,
        message="登出成功",
        status=200
    )


# 1.3 获取当前用户信息
@app.get("/auth/current-user", response_model=WrappedApiResponse)
async def get_current_user(user_id: int = Depends(get_user_id)):
    """
    获取当前用户信息接口
    文档来源: 千人千店api接口.md - 1.3 获取当前用户信息
    操作表: users
    功能: 根据用户ID获取用户信息
    参数: user_id(用户ID)
    返回: 用户完整信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=404, detail="用户不存在")

        user_data = {
            "id": str(user['id']),
            "account": user['account'],
            "password": user['password'],
            "name": user['name'],
            "role": user['role'],
            "avatar": user['avatar'] or "",
            "token": user['token'] or ""
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")


# 1.4 修改密码
@app.post("/auth/change-password", response_model=WrappedApiResponse)
async def change_password(request: ChangePasswordRequest, user_id: int = Depends(get_user_id)):
    """
    修改密码接口
    文档来源: 千人千店api接口.md - 1.4 修改密码
    操作表: users
    功能: 验证原密码并更新为新密码
    参数: oldPassword(原密码), newPassword(新密码), user_id(用户ID)
    返回: 修改成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 验证当前用户和旧密码
        cursor.execute("SELECT id FROM users WHERE id = %s AND password = %s",
                       (user_id, request.oldPassword))
        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=400, detail="原密码错误")

        # 更新密码
        cursor.execute("UPDATE users SET password = %s WHERE id = %s",
                       (request.newPassword, user_id))
        conn.commit()

        cursor.close()
        conn.close()

        # 与其他接口保持一致：使用两层 data 包装返回结果
        return create_wrapped_response(
            data={
                "data": {
                    "success": True
                }
            },
            message="密码修改成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"修改密码失败: {str(e)}")


# 1.5 更新用户资料
@app.put("/auth/profile", response_model=WrappedApiResponse)
async def update_profile(request: ProfileUpdateRequest, user_id: int = Depends(get_user_id)):
    """
    更新用户资料接口
    文档来源: 千人千店api接口.md - 1.5 更新用户资料
    操作表: users
    功能: 更新用户姓名和头像
    参数: name(姓名), avatar(头像), user_id(用户ID)
    返回: 更新后的用户信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查用户是否存在
        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=404, detail="用户不存在")

        # 更新用户信息
        update_fields = []
        update_values = []

        if request.name is not None:
            update_fields.append("name = %s")
            update_values.append(request.name)

        if request.avatar is not None:
            update_fields.append("avatar = %s")
            update_values.append(request.avatar)

        if update_fields:
            update_values.append(user_id)
            cursor.execute(f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s", update_values)
            conn.commit()

        # 获取更新后的用户信息
        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        updated_user = cursor.fetchone()

        user_data = {
            "id": str(updated_user['id']),
            "account": updated_user['account'],
            "password": updated_user['password'],
            "name": updated_user['name'],
            "role": updated_user['role'],
            "avatar": updated_user['avatar'] or "",
            "token": updated_user['token'] or ""
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新用户资料失败: {str(e)}")


# ==================== 日程相关接口 ====================

# 2.2 获取日程详情
@app.get("/schedules/{schedule_id}", response_model=WrappedApiResponse)
async def get_schedule(schedule_id: int, user_id: int = Depends(get_user_id)):
    """
    获取日程详情接口
    文档来源: 千人千店api接口.md - 2.2 获取日程详情
    操作表: schedules
    功能: 根据日程ID获取日程详情
    参数: schedule_id(日程ID)
    返回: 日程详细信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        cursor.execute("""
            SELECT s.*, c.name as customer_name 
            FROM schedules s 
            LEFT JOIN customers c ON s.customer_id = c.id 
            WHERE s.id = %s AND (s.created_by = %s OR c.owner = %s)
        """, (schedule_id, user_id, user_id))

        schedule = cursor.fetchone()

        if not schedule:
            raise HTTPException(status_code=404, detail="日程不存在")

        schedule_data = {
            "id": str(schedule['id']),
            "customerId": str(schedule['customer_id']) if schedule['customer_id'] else None,
            "customerName": schedule['customer_name'],
            "title": schedule['title'],
            "description": schedule['description'],
            "startTime": schedule['start_time'].isoformat() if schedule['start_time'] else None,
            "endTime": schedule['end_time'].isoformat() if schedule['end_time'] else None,
            "status": schedule['status'],
            "createdAt": schedule['created_at'].isoformat(),
            "updatedAt": schedule['updated_at'].isoformat()
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=schedule_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取日程详情失败: {str(e)}")


# 2.7 根据用户ID获取下属用户列表
@app.get("/department/users/{user_id}", response_model=WrappedApiResponse)
async def get_subordinate_users(user_id: int, current_user_id: int = Depends(get_user_id)):
    """
    获取下属用户列表接口
    文档来源: 千人千店api接口.md - 2.7 根据用户ID获取下属用户列表
    操作表: users
    功能: 根据用户ID获取下属用户列表（查询 users 表中 manager_id = user_id 的记录）
    参数: user_id(用户ID)
    返回: 下属用户列表
    说明: 通过 users 表的 manager_id 字段查询下属，manager_id 存储的是上级用户的ID
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以查看下属列表
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以查看任何用户的下属，普通用户只能查看自己的下属
        if current_user["role"] != 'admin' and current_user_id != user_id:
            raise HTTPException(status_code=403, detail="无权限查看该用户的下属列表")

        # 查询下属用户和用户自己：查询 users 表中 manager_id = user_id 的记录，以及用户自己（id = user_id）
        # manager_id 字段存储的是上级用户的ID
        # 用户自己也是自己的下级（自己也是自己的下级）
        cursor.execute("""
            SELECT id, name, avatar, account, role, department, status, created_at, updated_at
            FROM users 
            WHERE manager_id = %s OR id = %s
            ORDER BY id ASC
        """, (user_id, user_id))
        subordinates = cursor.fetchall()

        users_data = []
        for user in subordinates:
            users_data.append({
                "id": str(user['id']),
                "name": user['name'],
                "avatar": user.get('avatar') or "",
                "account": user.get('account') or "",
                "role": user.get('role') or "",
                "department": user.get('department') or "",
                "status": user.get('status') or "enabled",
                "createdAt": user.get('created_at').strftime("%Y-%m-%d %H:%M:%S") if user.get('created_at') else None,
                "updatedAt": user.get('updated_at').strftime("%Y-%m-%d %H:%M:%S") if user.get('updated_at') else None
            })

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=users_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取下属用户失败: {str(e)}")


# 2.13/2.14 删除门店日程接口（支持两种方式：根据计划ID或根据门店ID+日期）
@app.delete("/schedules/store/{id}", response_model=WrappedApiResponse)
async def delete_store_schedule(
        id: int,
        user_id: int = Depends(get_user_id),
        scheduleDate: Optional[str] = Query(None,
                                            description="计划日期（可选，如果提供则按门店ID+日期删除，否则按计划ID删除）")
):
    """
    删除门店日程接口
    文档来源: 千人千店api接口.md - 2.13/2.14 删除门店日程
    操作表: store_schedule_plans
    功能: 删除门店日程，支持两种方式：
        1. 如果提供scheduleDate参数：根据门店ID、用户ID和计划日期删除
        2. 如果不提供scheduleDate参数：根据门店计划ID删除
    参数:
        - id: 门店ID或门店计划ID
        - user_id: 用户ID（从请求头获取）
        - scheduleDate: 计划日期（可选）
    返回: 删除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        if scheduleDate:
            # 方式1：根据门店ID、用户ID和计划日期删除
            cursor.execute("""
                DELETE FROM store_schedule_plans 
                WHERE store_id = %s AND user_id = %s AND schedule_date = %s
            """, (id, user_id, scheduleDate))
            deleted_count = cursor.rowcount
            if deleted_count == 0:
                raise HTTPException(status_code=404, detail="未找到匹配的门店日程")
        else:
            # 方式2：根据门店计划ID删除
            # 先检查门店计划是否存在且属于当前用户
            cursor.execute(
                "SELECT store_schedule_plan_id FROM store_schedule_plans WHERE store_schedule_plan_id = %s AND user_id = %s",
                (id, user_id))
            if not cursor.fetchone():
                raise HTTPException(status_code=404, detail="门店计划不存在或无权限")

            cursor.execute("DELETE FROM store_schedule_plans WHERE store_schedule_plan_id = %s", (id,))
            deleted_count = cursor.rowcount

        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="删除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除门店日程失败: {str(e)}")


# 2.16 更新门店计划
@app.put("/store-schedule-plan/{store_schedule_plan_id}", response_model=WrappedApiResponse)
async def update_store_schedule_plan(store_schedule_plan_id: int, request: dict, user_id: int = Depends(get_user_id)):
    """
    更新门店计划接口
    文档来源: 千人千店api接口.md - 2.16 更新门店计划
    操作表: store_schedule_plans
    功能: 更新门店计划信息
    参数: store_schedule_plan_id(门店计划ID), 门店计划更新信息
    返回: 更新后的门店计划信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查门店计划是否存在并验证权限
        cursor.execute("SELECT * FROM store_schedule_plans WHERE store_schedule_plan_id = %s",
                       (store_schedule_plan_id,))
        plan = cursor.fetchone()

        if not plan:
            raise HTTPException(status_code=404, detail="门店计划不存在")

        # 验证权限：只有管理员或门店计划所有者可以修改计划
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以修改任何门店计划，普通用户只能修改自己的计划
        if current_user['role'] != 'admin' and user_id != plan['user_id']:
            raise HTTPException(status_code=403, detail="无权限修改该门店计划")

        # 更新门店计划
        cursor.execute("""
            UPDATE store_schedule_plans 
            SET store_id = %s, name = %s, address = %s, status = %s, 
                visited_status = %s, user_id = %s, schedule_date = %s, 
                type = %s, region = %s, remark = %s
            WHERE store_schedule_plan_id = %s
        """, (
            request.get('id'), request.get('name'), request.get('address'),
            request.get('status'), request.get('visitedstatus'), request.get('userId'),
            request.get('scheduleDate'), request.get('type'), request.get('region'),
            request.get('remark'), store_schedule_plan_id
        ))

        conn.commit()

        # 获取更新后的门店计划
        cursor.execute("SELECT * FROM store_schedule_plans WHERE store_schedule_plan_id = %s",
                       (store_schedule_plan_id,))
        updated_plan = cursor.fetchone()

        plan_data = {
            "storeSchedulePlanId": str(updated_plan['store_schedule_plan_id']),
            "id": str(updated_plan['store_id']) if updated_plan['store_id'] else None,
            "name": updated_plan['name'],
            "address": updated_plan['address'],
            "status": updated_plan['status'],
            "visitedstatus": updated_plan['visited_status'],
            "userId": str(updated_plan['user_id']),
            "scheduleDate": updated_plan['schedule_date'].isoformat() if updated_plan['schedule_date'] else None,
            "type": updated_plan['type'],
            "region": updated_plan['region'],
            "remark": updated_plan['remark']
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=plan_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新门店计划失败: {str(e)}")


# 2.17 根据用户ID和日期范围获取访问店面记录
@app.get("/user-visit-records/{user_id}")
async def get_user_visit_records(user_id: int, minDate: str = Query(...), maxDate: str = Query(...),
                                 current_user_id: int = Depends(get_user_id)):
    """
    获取用户访问记录接口
    文档来源: 千人千店api接口.md - 2.17 根据用户ID和日期范围获取访问店面记录
    操作表: store_schedule_plans
    功能: 根据用户ID和日期范围获取访问店面记录（从store_schedule_plans表中提取日期并去重）
    参数: user_id(用户ID), minDate(开始日期), maxDate(结束日期)
    返回: 访问记录列表，格式：{ data: [{date: '2025-11-01', info: '到访'}, ...] }
    说明: 数据来源是/store_schedule_plans表，提取schedule_date字段并去重
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以查看访问记录
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以查看任何用户的访问记录，普通用户只能查看自己的访问记录
        if current_user['role'] != 'admin' and current_user_id != user_id:
            raise HTTPException(status_code=403, detail="无权限查看该用户的访问记录")

        # 从store_schedule_plans表中查询指定日期范围内的日程日期（去重）
        # 这个接口的数据来源是/stores接口，提取schedule_date字段并去重
        cursor.execute("""
            SELECT DISTINCT schedule_date 
            FROM store_schedule_plans 
            WHERE user_id = %s AND schedule_date BETWEEN %s AND %s
            ORDER BY schedule_date
        """, (user_id, minDate, maxDate))

        records = cursor.fetchall()

        # 调试信息：打印查询结果
        print(f"[DEBUG] 用户ID: {user_id}, 日期范围: {minDate} 到 {maxDate}")
        print(f"[DEBUG] 从store_schedule_plans表查询到的记录数: {len(records)}")

        # 处理日期格式转换，构造包含date和info字段的数据
        data_list = []
        for record in records:
            schedule_date = record.get('schedule_date')
            if schedule_date:
                # 转换日期格式
                if isinstance(schedule_date, str):
                    date_str = schedule_date
                elif hasattr(schedule_date, 'isoformat'):
                    # 如果是date或datetime对象，转换为字符串
                    date_str = schedule_date.isoformat()
                else:
                    # 其他情况，尝试转换为字符串
                    date_str = str(schedule_date)

                # 添加date和info字段，info字段不能为空，默认为"到访"
                data_list.append({
                    "date": date_str,
                    "info": "到访"
                })

        cursor.close()
        conn.close()

        # 返回格式：{ data: [{date: '2025-11-01', info: '到访'}, ...] }
        # 直接返回字典，不进行双重包装
        return {
            "data": data_list
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取访问记录失败: {str(e)}")


# ==================== 回访记录相关接口 ====================
# tod 报错说:==================================================================================================================
'''
Error: 保存回访记录失败: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`qrqd`.`visit_records`, CONSTRAINT `visit_records_ibfk_1` FOREIGN KEY (`store_id`) REFERENCES `customers` (`id`) ON DELETE CASCADE)')'''


# 2.18 保存回访记录
@app.post("/visit-records", response_model=WrappedApiResponse)
async def save_visit_record(request: VisitRecordRequest, user_id: int = Depends(get_user_id)):
    """
    保存回访记录接口
    文档来源: 千人千店api接口.md - 2.18 保存回访记录
    操作表: visit_records
    功能: 保存客户回访记录
    参数: 回访记录完整信息(name, region, address, contactPerson, contactMethod, status, attribute, type, area, memberCount, coachCount, addedCoachCount, storeActions, actionValues, remarks, plan, checkInPhoto, createdBy, createdId(已弃用), avatar)
    返回: 保存的回访记录信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 处理合同期限
        contract_start = None
        contract_end = None
        if request.contractPeriod:
            if request.contractPeriod.get('startMonth'):
                start_month_str = request.contractPeriod['startMonth']
                # 处理日期字符串格式：可能是 "2026-12-01" 或 "2026-12-01T00:00:00.000Z"
                if isinstance(start_month_str, str):
                    # 移除时区信息（如果有）
                    start_month_str = start_month_str.replace('Z', '').replace('+00:00', '').split('T')[0]
                    # 转换为日期对象
                    contract_start = datetime.strptime(start_month_str, '%Y-%m-%d').date()
                elif hasattr(start_month_str, 'isoformat'):
                    # 如果已经是date或datetime对象
                    contract_start = start_month_str if isinstance(start_month_str, date) else start_month_str.date()
            if request.contractPeriod.get('endMonth'):
                end_month_str = request.contractPeriod['endMonth']
                # 处理日期字符串格式：可能是 "2023-10-01" 或 "2023-10-01T00:00:00.000Z"
                if isinstance(end_month_str, str):
                    # 移除时区信息（如果有）
                    end_month_str = end_month_str.replace('Z', '').replace('+00:00', '').split('T')[0]
                    # 转换为日期对象
                    contract_end = datetime.strptime(end_month_str, '%Y-%m-%d').date()
                elif hasattr(end_month_str, 'isoformat'):
                    # 如果已经是date或datetime对象
                    contract_end = end_month_str if isinstance(end_month_str, date) else end_month_str.date()

        # 处理门店活动
        store_actions = request.storeActions or {}
        action_values = request.actionValues or {}
        
        # 处理联系方式：如果前端传了contactWay且没传contactMethod，则使用contactWay的值
        contact_method = request.contactMethod
        if not contact_method and request.contactWay:
            contact_method = request.contactWay

        # 注意：created_id 字段已弃用，插入时使用 NULL
        # created_by 字段存储用户ID（字符串形式，如 "4"）
        cursor.execute("""
            INSERT INTO visit_records (
                store_id, name, is_new, ownership, region, address, contact_person,
                contact_method, status, contract_start_month, contract_end_month,
                attribute, type, area, member_count, coach_count, added_coach_count,
                coach_social, trial_activity, activity_embed, sponsorship_gift,
                banner_placement, display_case_placement, coach_social_count,
                trial_activity_count, activity_embed_count, sponsorship_gift_details,
                banner_placement_quantity, display_case_placement_quantity,
                remarks, plan, check_in_photo, created_by, created_id, avatar
            ) VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
            )
        """, (
            request.id, request.name, request.isNew, request.ownership, request.region,
            request.address, request.contactPerson, contact_method, request.status,
            contract_start, contract_end, request.attribute, request.type, request.area,
            request.memberCount, request.coachCount, request.addedCoachCount,
            store_actions.get('coachSocial', False), store_actions.get('trialActivity', False),
            store_actions.get('activityEmbed', False), store_actions.get('sponsorshipGift', False),
            store_actions.get('bannerPlacement', False), store_actions.get('displayCasePlacement', False),
            action_values.get('coachSocialCount', 0), action_values.get('trialActivityCount', 0),
            action_values.get('activityEmbedCount', 0), action_values.get('sponsorshipGiftDetails', ''),
            action_values.get('bannerPlacementQuantity', 0), action_values.get('displayCasePlacementQuantity', 0),
            request.remarks, request.plan, request.checkInPhoto, 
            request.createdBy or str(user_id),  # 使用 createdBy 或当前用户ID（转为字符串）
            None,  # created_id 字段已弃用，插入 NULL
            request.avatar
        ))

        record_id = cursor.lastrowid

        # 如果提供了store_id（request.id），同步更新customers表中的所有相关字段
        # 同步的字段包括：name, region, address, contact_person, contact_method, status, 
        # attribute, type, area, member_count, coach_count, added_coach_count, is_new, ownership,
        # contract_start_month, contract_end_month, 所有门店活动字段和活动数值字段,
        # remarks, plan, check_in_photo
        # 注意：created_by, avatar 字段不更新，保持客户创建时的原始信息
        # created_id 字段已弃用，不需要更新
        if request.id:
            try:
                # 构建更新字段列表
                update_fields = []
                update_params = []
                
                # 更新基本字段
                if request.name is not None:
                    update_fields.append("name = %s")
                    update_params.append(request.name)
                if request.region is not None:
                    # 处理region字段：将 / 替换为空格，统一格式
                    region_value = request.region.replace('/', ' ').strip() if request.region else None
                    update_fields.append("region = %s")
                    update_params.append(region_value)
                if request.address is not None:
                    update_fields.append("address = %s")
                    update_params.append(request.address)
                if request.contactPerson is not None:
                    update_fields.append("contact_person = %s")
                    update_params.append(request.contactPerson)
                # 处理联系方式：如果前端传了contactWay且没传contactMethod，则使用contactWay的值
                contact_method_value = request.contactMethod
                if not contact_method_value and request.contactWay:
                    contact_method_value = request.contactWay
                if contact_method_value is not None:
                    update_fields.append("contact_method = %s")
                    update_params.append(contact_method_value)
                if request.status is not None:
                    update_fields.append("status = %s")
                    update_params.append(request.status)
                if request.attribute is not None:
                    update_fields.append("attribute = %s")
                    update_params.append(request.attribute)
                if request.type is not None:
                    update_fields.append("type = %s")
                    update_params.append(request.type)
                if request.area is not None:
                    update_fields.append("area = %s")
                    update_params.append(request.area)
                if request.memberCount is not None:
                    update_fields.append("member_count = %s")
                    update_params.append(request.memberCount)
                if request.coachCount is not None:
                    update_fields.append("coach_count = %s")
                    update_params.append(request.coachCount)
                if request.addedCoachCount is not None:
                    update_fields.append("added_coach_count = %s")
                    update_params.append(request.addedCoachCount)
                if request.isNew is not None:
                    update_fields.append("is_new = %s")
                    update_params.append(request.isNew)
                if request.ownership is not None:
                    update_fields.append("ownership = %s")
                    update_params.append(request.ownership)
                
                # 更新合同期限
                if contract_start is not None:
                    update_fields.append("contract_start_month = %s")
                    update_params.append(contract_start)
                if contract_end is not None:
                    update_fields.append("contract_end_month = %s")
                    update_params.append(contract_end)
                
                # 更新门店活动字段（布尔值，False也是有效值）
                if 'coachSocial' in store_actions:
                    update_fields.append("coach_social = %s")
                    update_params.append(store_actions.get('coachSocial', False))
                if 'trialActivity' in store_actions:
                    update_fields.append("trial_activity = %s")
                    update_params.append(store_actions.get('trialActivity', False))
                if 'activityEmbed' in store_actions:
                    update_fields.append("activity_embed = %s")
                    update_params.append(store_actions.get('activityEmbed', False))
                if 'sponsorshipGift' in store_actions:
                    update_fields.append("sponsorship_gift = %s")
                    update_params.append(store_actions.get('sponsorshipGift', False))
                if 'bannerPlacement' in store_actions:
                    update_fields.append("banner_placement = %s")
                    update_params.append(store_actions.get('bannerPlacement', False))
                if 'displayCasePlacement' in store_actions:
                    update_fields.append("display_case_placement = %s")
                    update_params.append(store_actions.get('displayCasePlacement', False))
                
                # 更新活动数值字段（数值类型，0也是有效值）
                if 'coachSocialCount' in action_values:
                    update_fields.append("coach_social_count = %s")
                    update_params.append(action_values.get('coachSocialCount', 0))
                if 'trialActivityCount' in action_values:
                    update_fields.append("trial_activity_count = %s")
                    update_params.append(action_values.get('trialActivityCount', 0))
                if 'activityEmbedCount' in action_values:
                    update_fields.append("activity_embed_count = %s")
                    update_params.append(action_values.get('activityEmbedCount', 0))
                if 'sponsorshipGiftDetails' in action_values:
                    update_fields.append("sponsorship_gift_details = %s")
                    update_params.append(action_values.get('sponsorshipGiftDetails', ''))
                if 'bannerPlacementQuantity' in action_values:
                    update_fields.append("banner_placement_quantity = %s")
                    update_params.append(action_values.get('bannerPlacementQuantity', 0))
                if 'displayCasePlacementQuantity' in action_values:
                    update_fields.append("display_case_placement_quantity = %s")
                    update_params.append(action_values.get('displayCasePlacementQuantity', 0))
                
                # 更新备注和计划
                if request.remarks is not None:
                    update_fields.append("remarks = %s")
                    update_params.append(request.remarks)
                if request.plan is not None:
                    update_fields.append("plan = %s")
                    update_params.append(request.plan)
                if request.checkInPhoto is not None:
                    update_fields.append("check_in_photo = %s")
                    update_params.append(request.checkInPhoto)
                
                # 更新更新时间
                update_fields.append("updated_at = NOW()")
                
                # 执行更新
                if update_fields:
                    update_params.append(request.id)  # WHERE id = %s
                    update_sql = f"UPDATE customers SET {', '.join(update_fields)} WHERE id = %s"
                    cursor.execute(update_sql, update_params)
                    print(f"已同步更新客户ID {request.id} 的所有相关字段")
                else:
                    print(f"没有需要更新的字段")
            except Exception as e:
                import traceback
                error_msg = f"更新客户信息失败: {str(e)}\n{traceback.format_exc()}"
                print(f"[ERROR] {error_msg}")
                # 不抛出异常，因为拜访记录已经创建成功，状态更新失败不应该影响整体流程

        conn.commit()

        # 获取创建的回访记录
        cursor.execute("SELECT * FROM visit_records WHERE id = %s", (record_id,))
        record = cursor.fetchone()

        # 构建返回数据
        # 根据表结构修正索引：
        # 0:id, 1:store_id, 2:name, 3:is_new, 4:ownership, 5:region, 6:address, 7:contact_person, 8:contact_method, 9:status
        # 10:contract_start_month, 11:contract_end_month, 12:attribute, 13:type, 14:area, 15:member_count, 16:coach_count, 17:added_coach_count
        # 18:coach_social, 19:trial_activity, 20:activity_embed, 21:sponsorship_gift, 22:banner_placement, 23:display_case_placement
        # 24:coach_social_count, 25:trial_activity_count, 26:activity_embed_count, 27:sponsorship_gift_details
        # 28:banner_placement_quantity, 29:display_case_placement_quantity, 30:remarks, 31:plan, 32:check_in_photo
        # 33:created_by, 34:created_id(已弃用), 35:avatar

        store_actions_data = {
            "coachSocial": bool(record[18]),
            "trialActivity": bool(record[19]),
            "activityEmbed": bool(record[20]),
            "sponsorshipGift": bool(record[21]),
            "bannerPlacement": bool(record[22]),
            "displayCasePlacement": bool(record[23])
        }

        action_values_data = {
            "coachSocialCount": record[24],
            "trialActivityCount": record[25],
            "activityEmbedCount": record[26],
            "sponsorshipGiftDetails": record[27],
            "bannerPlacementQuantity": record[28],
            "displayCasePlacementQuantity": record[29]
        }

        contract_period = None
        # 修正索引：contract_start_month 是 record[10]，contract_end_month 是 record[11]
        # record[9] 是 status，不是日期字段
        if record[10] and record[11]:  # contract_start_month and contract_end_month
            # 处理日期对象或字符串
            start_month = record[10]
            end_month = record[11]

            # 如果是date对象，使用isoformat；如果是字符串，直接使用
            if hasattr(start_month, 'isoformat'):
                start_month_str = start_month.isoformat()
            else:
                start_month_str = str(start_month) if start_month else None

            if hasattr(end_month, 'isoformat'):
                end_month_str = end_month.isoformat()
            else:
                end_month_str = str(end_month) if end_month else None

            if start_month_str and end_month_str:
                contract_period = {
                    "startMonth": start_month_str,
                    "endMonth": end_month_str
                }

        record_data = {
            "id": str(record[1]) if record[1] else str(request.id),  # 返回store_id（门店/客户ID），而不是回访记录ID
            "visitRecordId": str(record[0]),  # 新增：回访记录ID（如果需要的话）
            "name": record[2],
            "isNew": bool(record[3]),
            "ownership": record[4],
            "region": record[5],
            "address": record[6],
            "contactPerson": record[7],
            "contactMethod": record[8],
            "status": record[9],  # status 字段
            "contractPeriod": contract_period,
            "attribute": record[12],  # 修正：attribute是record[12]
            "type": record[13],  # 修正：type是record[13]
            "area": record[14],  # 修正：area是record[14]
            "memberCount": record[15],  # 修正：member_count是record[15]
            "coachCount": record[16],  # 修正：coach_count是record[16]
            "addedCoachCount": record[17],  # 修正：added_coach_count是record[17]
            "storeActions": store_actions_data,
            "actionValues": action_values_data,
            "remarks": record[30],  # 修正：remarks是record[30]
            "plan": record[31],  # 修正：plan是record[31]
            "checkInPhoto": record[32],  # 修正：check_in_photo是record[32]
            "createdBy": record[33],  # created_by是record[33]（用户ID，字符串形式）
            "createdId": None,  # created_id 字段已弃用，始终返回 None
            "avatar": record[35] if len(record) > 35 else None,  # avatar是record[35]
            "createdAt": None  # created_at字段不在SELECT *中，如果需要可以单独查询
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=record_data,
            message="回访记录保存成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"保存回访记录失败: {str(e)}")


# 2.19 根据门店ID获取回访记录
@app.get("/visit-records/{store_id}", response_model=WrappedApiResponse)
async def get_visit_records(
        store_id: int,
        page: int = Query(1, ge=1, description="页码"),
        pageSize: int = Query(10, ge=1, le=100, description="每页数量"),
        user_id: int = Depends(get_user_id)
):
    """
    获取回访记录接口（分页）
    文档来源: 千人千店api接口.md - 2.19 根据门店ID获取回访记录
    操作表: visit_records, customers, users, store_schedule_plans
    功能: 根据门店ID获取回访记录，支持分页
    参数: store_id(门店ID), page(页码), pageSize(每页数量)
    返回: 分页的回访记录列表
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或门店所有者可以查看回访记录
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查门店是否属于当前用户（通过customers表的owner字段）
        cursor.execute("SELECT owner FROM customers WHERE id = %s", (store_id,))
        store_owner = cursor.fetchone()
        if not store_owner:
            raise HTTPException(status_code=404, detail="门店不存在")

        # 检查权限：管理员可以查看任何门店的回访记录，普通用户只能查看自己的门店或公海门店（owner=0）
        owner_value = store_owner.get('owner') if isinstance(store_owner, dict) else store_owner[0]
        if current_user['role'] != 'admin' and user_id != owner_value and owner_value != 0:
            raise HTTPException(status_code=403, detail="无权限查看该门店的回访记录")

        # 获取总数
        cursor.execute("SELECT COUNT(*) as total FROM visit_records WHERE store_id = %s", (store_id,))
        total = cursor.fetchone()['total']

        # 计算分页
        offset = (page - 1) * pageSize

        # 获取分页数据，关联 customers 和 users 表获取客户信息和创建人信息
        # 注意：created_id 字段已弃用，使用 created_by 字段关联 users 表获取创建人信息
        # created_by 字段是 VARCHAR 类型，但存储的是用户ID（字符串形式，如 "4"）
        # 需要通过 CAST 或 CONVERT 将 created_by 转换为 INT 来关联 users 表
        cursor.execute("""
            SELECT 
                vr.id, vr.store_id, vr.name, vr.is_new, vr.ownership, vr.region, vr.address,
                vr.contact_person, vr.contact_method, vr.status,
                vr.contract_start_month, vr.contract_end_month,
                vr.attribute, vr.type, vr.area,
                vr.member_count, vr.coach_count, vr.added_coach_count,
                vr.coach_social, vr.trial_activity, vr.activity_embed, vr.sponsorship_gift,
                vr.banner_placement, vr.display_case_placement,
                vr.coach_social_count, vr.trial_activity_count, vr.activity_embed_count,
                vr.sponsorship_gift_details, vr.banner_placement_quantity, vr.display_case_placement_quantity,
                vr.remarks, vr.plan, vr.check_in_photo,
                vr.created_by, vr.avatar, vr.created_at,
                c.id as customer_id, c.name as customer_name,
                u.id as creator_id, u.name as creator_name, u.avatar as creator_avatar
            FROM visit_records vr
            LEFT JOIN customers c ON vr.store_id = c.id
            LEFT JOIN users u ON CAST(vr.created_by AS UNSIGNED) = u.id
            WHERE vr.store_id = %s
            ORDER BY vr.created_at DESC
            LIMIT %s OFFSET %s
        """, (store_id, pageSize, offset))
        records = cursor.fetchall()

        records_data = []
        for record in records:
            # 查找对应的 store_schedule_plan_id（根据 store_id、created_at 日期和 user_id）
            store_schedule_plan_id = None
            if record.get('created_at'):
                try:
                    schedule_date = record['created_at'].date() if hasattr(record['created_at'], 'date') else record['created_at']
                    # 获取创建回访记录的用户ID（使用 created_by 字段，已弃用 created_id）
                    # created_by 字段是 VARCHAR 类型，存储的是用户ID（字符串形式）
                    created_user_id_str = record.get('created_by')
                    if created_user_id_str:
                        try:
                            created_user_id = int(created_user_id_str)
                        except (ValueError, TypeError):
                            created_user_id = user_id
                    else:
                        created_user_id = user_id

                    # 更精确的查询：根据store_id、schedule_date和user_id查找，按创建时间倒序取第一个
                    cursor.execute("""
                        SELECT store_schedule_plan_id 
                        FROM store_schedule_plans 
                        WHERE store_id = %s AND schedule_date = %s AND user_id = %s
                        ORDER BY created_at DESC
                        LIMIT 1
                    """, (store_id, schedule_date, created_user_id))
                    plan_result = cursor.fetchone()
                    if plan_result and plan_result.get('store_schedule_plan_id') is not None:
                        plan_id = plan_result.get('store_schedule_plan_id')
                        store_schedule_plan_id = str(plan_id) if plan_id is not None else None
                    else:
                        # 如果没找到，尝试不限制user_id（兼容旧数据）
                        cursor.execute("""
                            SELECT store_schedule_plan_id 
                            FROM store_schedule_plans 
                            WHERE store_id = %s AND schedule_date = %s
                            ORDER BY created_at DESC
                            LIMIT 1
                        """, (store_id, schedule_date))
                        plan_result = cursor.fetchone()
                        if plan_result and plan_result.get('store_schedule_plan_id') is not None:
                            plan_id = plan_result.get('store_schedule_plan_id')
                            store_schedule_plan_id = str(plan_id) if plan_id is not None else None
                except Exception as e:
                    # 如果查询失败，继续处理，不影响主流程
                    print(f"Warning: Failed to get store_schedule_plan_id: {e}")
                    store_schedule_plan_id = None

            # 构建storeActions
            store_actions = {
                "coachSocial": bool(record.get('coach_social')),
                "trialActivity": bool(record.get('trial_activity')),
                "activityEmbed": bool(record.get('activity_embed')),
                "sponsorshipGift": bool(record.get('sponsorship_gift')),
                "bannerPlacement": bool(record.get('banner_placement')),
                "displayCasePlacement": bool(record.get('display_case_placement'))
            }

            # 构建actionValues
            action_values = {
                "coachSocialCount": record.get('coach_social_count') or 0,
                "trialActivityCount": record.get('trial_activity_count') or 0,
                "activityEmbedCount": record.get('activity_embed_count') or 0,
                "sponsorshipGiftDetails": record.get('sponsorship_gift_details') or "",
                "bannerPlacementQuantity": record.get('banner_placement_quantity') or 0,
                "displayCasePlacementQuantity": record.get('display_case_placement_quantity') or 0
            }

            # 构建contractPeriod
            contract_period = None
            if record.get('contract_start_month') or record.get('contract_end_month'):
                try:
                    start_month = record.get('contract_start_month')
                    end_month = record.get('contract_end_month')
                    contract_period = {
                        "startMonth": start_month.strftime('%Y-%m-%d') if start_month and hasattr(start_month,
                                                                                                  'strftime') else None,
                        "endMonth": end_month.strftime('%Y-%m-%d') if end_month and hasattr(end_month,
                                                                                            'strftime') else None
                    }
                except Exception as e:
                    print(f"Warning: Failed to format contract_period: {e}")
                    contract_period = None

            # 获取创建人信息（优先使用 users 表，如果没有则使用 visit_records 表的字段）
            # createdBy 应该是 created_by（用户ID），用于关联 users 表
            # 注意：created_id 字段已弃用，使用 created_by 字段
            # created_by 字段是 VARCHAR 类型，但存储的是用户ID（字符串形式，如 "4"）
            # 返回时直接返回 created_by 的值（用户ID）
            created_by_id = record.get('created_by')  # 用户ID（从 visit_records.created_by 获取，VARCHAR 类型但存储的是用户ID）
            created_by_name = record.get('creator_name') or ""  # 用户名称，从 users 表查询到的 name
            avatar_url = record.get('creator_avatar') or record.get('avatar') or ""  # 用户头像，优先使用 users 表的 avatar

            records_data.append({
                "storeSchedulePlanId": store_schedule_plan_id,
                "id": str(record.get('store_id', '')),
                "name": record.get('name') or record.get('customer_name') or "",
                "isNew": bool(record.get('is_new')),
                "ownership": record.get('ownership') or "",
                "region": record.get('region') or "",
                "address": record.get('address') or "",
                "avatar": avatar_url,  # 使用从 users 表查询到的 avatar（creator_avatar），如果没有则使用 visit_records 表的 avatar
                "createdBy": str(created_by_id) if created_by_id is not None else "",  # 创建人ID（users 表的 id），用于关联 users 表
                "createdByName": created_by_name,  # 创建人名称（从 users 表查询到的 name）
                "createdAt": record.get('created_at').strftime('%Y-%m-%d') if record.get('created_at') and hasattr(
                    record.get('created_at'), 'strftime') else None,
                "contactPerson": record.get('contact_person') or "",
                "contactWay": record.get('contact_method') or "",
                "status": record.get('status') or "",
                "contractPeriod": contract_period,
                "attribute": record.get('attribute') or "",
                "type": record.get('type') or "",
                "area": record.get('area') or "",
                "memberCount": record.get('member_count') or 0,
                "coachCount": record.get('coach_count') or 0,
                "addedCoachCount": record.get('added_coach_count') or 0,
                "storeActions": store_actions,
                "actionValues": action_values,
                "remarks": record.get('remarks') or "",
                "plan": record.get('plan') or "",
                "checkInPhoto": record.get('check_in_photo') or ""
            })

        cursor.close()
        conn.close()

        # 直接返回数组格式（和之前一样）
        return create_wrapped_response(
            data=records_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        import traceback
        error_detail = str(e)
        print(f"Error in get_visit_records: {error_detail}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=f"获取回访记录失败: {error_detail}")


# 文件上传接口

# 2.21 保存图片URL
@app.post("/upload/image", response_model=WrappedApiResponse)
async def save_image_url(request: ImageUrlRequest, user_id: int = Depends(get_user_id)):
    """
    保存图片URL接口
    文档来源: 千人千店api接口.md - 2.21 上传图片
    操作表: file_uploads
    功能: 保存前端上传到OSS后的图片URL到数据库
    参数: imageUrl(图片URL地址), fileName(文件名，可选)
    返回: 保存结果和文件信息
    """
    try:
        # 生成文件ID
        file_id = str(uuid.uuid4())

        # 如果没有提供文件名，从URL中提取
        if not request.fileName:
            # 从URL中提取文件名
            file_name = request.imageUrl.split('/')[-1]
            if '.' not in file_name:
                file_name = f"{file_id}.jpg"
        else:
            file_name = request.fileName

        # 保存文件信息到数据库
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("""
            INSERT INTO file_uploads (file_id, file_name, file_url)
            VALUES (%s, %s, %s)
        """, (file_id, file_name, request.imageUrl))

        conn.commit()
        cursor.close()
        conn.close()

        data = {
            "fileId": file_id,
            "fileName": file_name,
            "url": request.imageUrl,
            "uploadedAt": datetime.now().isoformat()
        }

        return create_wrapped_response(
            data=data,
            message="保存成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"保存图片URL失败: {str(e)}")


# 2.22 删除图片
@app.delete("/upload/image/{file_id}", response_model=WrappedApiResponse)
async def delete_image(file_id: str, user_id: int = Depends(get_user_id)):
    """
    删除图片接口
    文档来源: 千人千店api接口.md - 2.22 删除图片
    操作表: file_uploads
    功能: 删除图片文件
    参数: file_id(文件ID)
    返回: 删除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 先检查文件是否存在并获取文件所有者
        cursor.execute("SELECT user_id FROM file_uploads WHERE file_id = %s", (file_id,))
        file_info = cursor.fetchone()

        if not file_info:
            raise HTTPException(status_code=404, detail="文件不存在")

        # 验证权限：只有管理员或文件所有者可以删除文件
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以删除任何文件，普通用户只能删除自己的文件
        if current_user['role'] != 'admin' and user_id != file_info[0]:
            raise HTTPException(status_code=403, detail="无权限删除该文件")

        cursor.execute("DELETE FROM file_uploads WHERE file_id = %s", (file_id,))

        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="删除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除图片失败: {str(e)}")


# 数据分析接口

# 4.1 获取分析数据
# ==================== 数据分析相关接口 ====================

@app.get("/analytics", response_model=WrappedApiResponse)
async def get_analytics(user_id: int = Depends(get_user_id)):
    """
    获取分析数据接口
    操作表: customers, schedules, visit_records, users
    功能: 获取系统分析数据，包括客户统计、日程统计、回访统计等
    参数: 无
    返回: 分析数据统计信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 获取客户总数（只统计该用户的客户）
        cursor.execute("SELECT COUNT(*) as total FROM customers WHERE owner = %s", (user_id,))
        total_customers = cursor.fetchone()['total']

        # 获取回访记录总数（只统计该用户的回访记录，使用created_by字段）
        cursor.execute("""
            SELECT COUNT(*) as total FROM visit_records
            WHERE CAST(created_by AS UNSIGNED) = %s
        """, (user_id,))
        total_visits = cursor.fetchone()['total']

        # 获取已签约客户数（只统计该用户的已签约客户）
        cursor.execute("SELECT COUNT(*) as total FROM customers WHERE status = '已签约' AND owner = %s", (user_id,))
        signed_customers = cursor.fetchone()['total']

        # 计算转化率
        conversion_rate = signed_customers / total_customers if total_customers > 0 else 0

        # 获取热门区域（只统计该用户的客户）
        cursor.execute("""
            SELECT region, COUNT(*) as count 
            FROM customers 
            WHERE region IS NOT NULL AND owner = %s
            GROUP BY region 
            ORDER BY count DESC 
            LIMIT 5
        """, (user_id,))
        top_regions = [row['region'] for row in cursor.fetchall()]

        # 获取客户状态分布（只统计该用户的客户）
        cursor.execute("""
            SELECT status, COUNT(*) as count 
            FROM customers 
            WHERE owner = %s
            GROUP BY status
        """, (user_id,))
        status_distribution = {row['status']: row['count'] for row in cursor.fetchall()}

        # 获取最近7天的访问记录（只统计该用户的回访记录，使用created_by字段）
        cursor.execute("""
            SELECT DATE(created_at) as date, COUNT(*) as count 
            FROM visit_records
            WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND CAST(created_by AS UNSIGNED) = %s
            GROUP BY DATE(created_at)
            ORDER BY date
        """, (user_id,))
        recent_visits = {row['date'].isoformat(): row['count'] for row in cursor.fetchall()}

        analytics_data = {
            "totalCustomers": total_customers,
            "totalVisits": total_visits,
            "signedCustomers": signed_customers,
            "conversionRate": round(conversion_rate, 3),
            "topRegions": top_regions,
            "statusDistribution": status_distribution,
            "recentVisits": recent_visits
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=analytics_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取分析数据失败: {str(e)}")


# 额外的实用接口

# 获取所有用户列表
# ==================== 实用接口 ====================

@app.get("/users", response_model=WrappedApiResponse)
async def get_users(user_id: int = Depends(get_user_id)):
    """
    获取所有用户列表接口
    操作表: users
    功能: 获取系统中所有用户列表
    参数: 无
    返回: 用户列表数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 只返回当前用户信息，不返回所有用户（安全考虑）
        cursor.execute("SELECT id, account, name, role, avatar FROM users WHERE id = %s", (user_id,))
        users = cursor.fetchall()

        users_data = []
        for user in users:
            users_data.append({
                "id": str(user['id']),
                "account": user['account'],
                "name": user['name'],
                "role": user['role'],
                "avatar": user['avatar'] or ""
            })

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=users_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户列表失败: {str(e)}")


# 获取门店计划列表
@app.get("/store-schedule-plans", response_model=WrappedApiResponse)
async def get_store_schedule_plans(
        userId: Optional[int] = Query(None),
        scheduleDate: Optional[str] = Query(None),
        page: int = Query(1, ge=1),
        size: int = Query(10, ge=1, le=100),
        user_id: int = Depends(get_user_id)
):
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建查询条件
        where_conditions = []
        params = []

        # 添加用户权限过滤 - 只显示该用户的门店计划
        where_conditions.append("user_id = %s")
        params.append(user_id)

        if userId:
            where_conditions.append("user_id = %s")
            params.append(userId)

        if scheduleDate:
            where_conditions.append("schedule_date = %s")
            params.append(scheduleDate)

        where_clause = " AND ".join(where_conditions) if where_conditions else "1=1"

        # 获取总数
        cursor.execute(f"SELECT COUNT(*) as total FROM store_schedule_plans WHERE {where_clause}", params)
        total = cursor.fetchone()['total']

        # 获取分页数据
        offset = (page - 1) * size
        cursor.execute(f"""
            SELECT * FROM store_schedule_plans 
            WHERE {where_clause}
            ORDER BY created_at DESC
            LIMIT %s OFFSET %s
        """, params + [size, offset])

        plans = cursor.fetchall()

        plans_data = []
        for plan in plans:
            plans_data.append({
                "storeSchedulePlanId": str(plan['store_schedule_plan_id']),
                "id": str(plan['store_id']) if plan['store_id'] else None,
                "name": plan['name'],
                "address": plan['address'],
                "status": plan['status'],
                "visitedstatus": plan['visited_status'],
                "userId": str(plan['user_id']),
                "scheduleDate": plan['schedule_date'].isoformat() if plan['schedule_date'] else None,
                "type": plan['type'],
                "region": plan['region'],
                "remark": plan['remark'],
                "createdAt": plan['created_at'].isoformat(),
                "updatedAt": plan['updated_at'].isoformat()
            })

        result = {
            "data": plans_data,
            "pagination": {
                "page": page,
                "size": size,
                "total": total,
                "pages": (total + size - 1) // size
            }
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=result,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取门店计划列表失败: {str(e)}")


# 健康检查接口
@app.get("/health", response_model=WrappedApiResponse)
async def health_check(user_id: int = Depends(get_user_id)):
    """
    健康检查接口
    操作表: 无
    功能: 检查系统健康状态，包括数据库连接
    参数: 无
    返回: 系统健康状态信息
    """
    try:
        # 测试数据库连接
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute("SELECT 1")
        cursor.fetchone()
        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={"status": "healthy", "database": "connected"},
            message="服务正常",
            status=200
        )
    except Exception as e:
        return create_wrapped_response(
            data={"status": "unhealthy", "error": str(e)},
            message="服务异常",
            status=500
        )


# 获取系统统计信息
@app.get("/stats", response_model=WrappedApiResponse)
async def get_system_stats(user_id: int = Depends(get_user_id)):
    """
    获取系统统计接口
    操作表: users, customers, schedules, visit_records
    功能: 获取系统统计数据，包括用户数、客户数、日程数、回访记录数等
    参数: 无
    返回: 系统统计数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 获取各种统计数据
        stats = {}

        # 我的客户（totalUsers）
        cursor.execute("SELECT COUNT(*) as count FROM customers WHERE owner = %s", (user_id,))
        stats['totalUsers'] = cursor.fetchone()['count']

        # 动销单数（myOrders）- 假设基于回访记录或订单表
        cursor.execute("""
            SELECT COUNT(*) as count FROM visit_records v
            LEFT JOIN customers c ON v.store_id = c.id
            WHERE c.owner = %s
        """, (user_id,))
        stats['myOrders'] = cursor.fetchone()['count']

        # 动销金额（mySales）- 假设基于回访记录数量作为金额（示例）
        cursor.execute("""
            SELECT COUNT(*) as count FROM visit_records v
            LEFT JOIN customers c ON v.store_id = c.id
            WHERE c.owner = %s
        """, (user_id,))
        # 假设每个回访记录代表1000元的销售额
        visit_count = cursor.fetchone()['count']
        stats['mySales'] = visit_count * 1000

        # 签约客户（myContracts）- 假设基于客户状态为已签约的客户
        cursor.execute("""
            SELECT COUNT(*) as count FROM customers 
            WHERE owner = %s AND status = '已签约'
        """, (user_id,))
        stats['myContracts'] = cursor.fetchone()['count']

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=stats,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取系统统计失败: {str(e)}")


# ==================== 新增客户API接口 ====================

# 1.1 获取客户列表 (支持分页)
@app.get("/customers", response_model=WrappedApiResponse)
async def get_customers_new(
        id: Optional[int] = Query(None, description="客户ID筛选（精确匹配）"),
        region: Optional[str] = Query(None, description="区域筛选"),
        attribute: Optional[str] = Query(None, description="属性分类筛选"),
        type: Optional[str] = Query(None, description="类型分类筛选"),
        status: Optional[str] = Query(None, description="客户状态筛选"),
        searchQuery: Optional[str] = Query(None, description="搜索关键词"),
        ownership: Optional[str] = Query(None, description="归属类型筛选"),
        owner: Optional[int] = Query(None, description="所有者ID筛选"),
        page: int = Query(1, description="页码"),
        pageSize: int = Query(10, description="每页数量"),
        user_id: int = Depends(get_user_id)
):
    """
    获取客户列表接口
    文档来源: 千人千店客户api接口.md - 1.1 获取客户列表
    操作表: customers
    功能: 获取客户列表，支持多条件筛选、搜索和分页
    参数: region(区域), attribute(属性), type(类型), status(状态), searchQuery(搜索关键词), ownership(归属类型), page(页码), pageSize(每页数量)
    返回: 分页的客户列表数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建查询条件
        where_conditions = []
        params = []

        # 如果指定了id，只查询该客户（优先级最高）
        if id is not None:
            where_conditions.append("id = %s")
            params.append(id)
        else:
            # 添加用户权限过滤 - 根据owner参数决定筛选逻辑
            if owner is not None:
                if owner == 0:
                    # owner=0表示查询owner为0的记录（客户公海）
                    where_conditions.append("owner = 0")
                else:
                    # 指定owner ID
                    where_conditions.append("owner = %s")
                    params.append(owner)
            else:
                # 默认查询客户公海（owner为0的客户）
                where_conditions.append("owner = 0")

        if region:
            # 处理region参数的特殊格式
            if region == "全国":
                # "全国"表示不限制region，不添加region条件
                pass
            elif "/全部" in region:
                # 支持三种格式：
                # 1. "安徽/全部" -> 查询所有region以"安徽"开头的记录（省份级别）
                # 2. "湖南/长沙市/全部" -> 查询所有region以"湖南省 长沙市"开头的记录（省/市级别）
                # 3. "北京/北京市/全部" -> 查询所有region以"北京市 北京市"开头的记录（省/市级别，但省和市名称相同）
                region_prefix = region.replace("/全部", "")
                
                # 判断是省份级别、省/市级别还是省/市/区级别
                parts = region_prefix.split("/")
                
                if len(parts) >= 3:
                    # 三级区域：省/市/全部，如 "北京/北京市/全部"（实际上region_prefix是"北京/北京市"，parts长度是2）
                    # 这种情况应该和二级区域一起处理
                    province = parts[0]  # "北京"
                    city = parts[1]      # "北京市"
                    # 检查是否是直辖市（province在city中，如"北京"在"北京市"中）
                    if province in city or city.startswith(province):
                        # 直辖市情况：匹配所有以该城市开头的记录（包括"北京市 北京市 东城区"等格式）
                        # 支持多种格式：空格分隔、斜杠分隔、带"省"字等
                        where_conditions.append("(region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s)")
                        params.append(f"{city} {city}%")      # 北京市 北京市%
                        params.append(f"{city}/{city}%")      # 北京市/北京市%
                        params.append(f"{city}省 {city}%")     # 北京市省 北京市%
                        params.append(f"{city}省/{city}%")     # 北京市省/北京市%
                        params.append(f"{city}%")               # 北京市%（更宽泛的匹配）
                        params.append(f"%{city}%")             # %北京市%（包含匹配，确保能匹配到）
                    else:
                        # 普通省/市，使用 build_city_like_condition 辅助函数支持4种格式匹配
                        city_condition, city_params = build_city_like_condition(province, city, "region")
                        where_conditions.append(city_condition)
                        params.extend(city_params)
                elif len(parts) == 2:
                    # 省/市级别：如 "湖南/长沙市" 或 "北京/北京市"（去掉/全部后）
                    province = parts[0]  # "湖南" 或 "北京"
                    city = parts[1]      # "长沙市" 或 "北京市"
                    # 检查是否是直辖市（province在city中，如"北京"在"北京市"中）
                    if province in city or city.startswith(province):
                        # 直辖市情况：匹配所有以该城市开头的记录（包括"北京市 北京市 东城区"等格式）
                        # 支持多种格式：空格分隔、斜杠分隔、带"省"字等
                        where_conditions.append("(region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s OR region LIKE %s)")
                        params.append(f"{city} {city}%")      # 北京市 北京市%
                        params.append(f"{city}/{city}%")      # 北京市/北京市%
                        params.append(f"{city}省 {city}%")     # 北京市省 北京市%
                        params.append(f"{city}省/{city}%")     # 北京市省/北京市%
                        params.append(f"{city}%")               # 北京市%（更宽泛的匹配）
                        params.append(f"%{city}%")             # %北京市%（包含匹配，确保能匹配到）
                    else:
                        # 普通省/市，使用 build_city_like_condition 辅助函数支持4种格式匹配
                        city_condition, city_params = build_city_like_condition(province, city, "region")
                        where_conditions.append(city_condition)
                        params.extend(city_params)
                else:
                    # 省份级别：如 "安徽"
                    # 使用 build_region_like_condition 辅助函数支持4种格式匹配
                    region_condition, region_params = build_region_like_condition(region_prefix, "region")
                    where_conditions.append(region_condition)
                    params.extend(region_params)
            else:
                # 处理三级区域（省/市/区）或其他格式
                # 如果包含多个"/"，说明是三级区域，如 "四川/乐山市/市中区"
                if region.count("/") >= 2:
                    # 三级区域：省/市/区
                    parts = region.split("/")
                    if len(parts) >= 3:
                        province = parts[0]  # "四川"
                        city = parts[1]      # "乐山市"
                        district = parts[2]   # "市中区"
                        # 使用包含匹配，因为数据库格式可能是 "四川省 乐山市 市中区"
                        # 需要匹配所有包含这三个部分的记录
                        where_conditions.append("(region LIKE %s AND region LIKE %s AND region LIKE %s)")
                        params.append(f"%{province}%")
                        params.append(f"%{city}%")
                        params.append(f"%{district}%")
                    elif len(parts) == 2:
                        # 二级区域：省/市，使用 build_city_like_condition
                        province = parts[0]
                        city = parts[1]
                        city_condition, city_params = build_city_like_condition(province, city, "region")
                        where_conditions.append(city_condition)
                        params.extend(city_params)
                    else:
                        # 格式不对，使用包含匹配
                        where_conditions.append("region LIKE %s")
                        params.append(f"%{region}%")
                elif "/" in region:
                    # 二级区域：省/市
                    parts = region.split("/")
                    if len(parts) >= 2:
                        province = parts[0]
                        city = parts[1]
                        city_condition, city_params = build_city_like_condition(province, city, "region")
                        where_conditions.append(city_condition)
                        params.extend(city_params)
                    else:
                        # 格式不对，使用包含匹配
                        where_conditions.append("region LIKE %s")
                        params.append(f"%{region}%")
                else:
                    # 单级区域或精确匹配，先尝试精确匹配，如果失败可以使用包含匹配
                    # 但为了兼容性，直接使用包含匹配更安全
                    where_conditions.append("region LIKE %s")
                    params.append(f"%{region}%")

        if attribute:
            where_conditions.append("attribute = %s")
            params.append(attribute)

        if type:
            where_conditions.append("type = %s")
            params.append(type)

        if status:
            where_conditions.append("status = %s")
            params.append(status)

        if ownership:
            where_conditions.append("ownership = %s")
            params.append(ownership)

        if searchQuery:
            # 扩展搜索范围：客户名称、联系人、联系方式、地址、区域
            where_conditions.append("(name LIKE %s OR contact_person LIKE %s OR contact_method LIKE %s OR address LIKE %s OR region LIKE %s)")
            search_param = f"%{searchQuery}%"
            params.extend([search_param, search_param, search_param, search_param, search_param])

        # 构建WHERE子句
        where_clause = ""
        if where_conditions:
            where_clause = "WHERE " + " AND ".join(where_conditions)

        # 如果指定了id，直接返回单条数据（不使用分页）
        if id is not None:
            # 获取单条数据
            sql = f"""
                SELECT 
                    id, name, is_new, ownership, region, address, contact_person, contact_method,
                    status, contract_start_month, contract_end_month, attribute, type, area,
                    member_count, coach_count, added_coach_count,
                    coach_social, trial_activity, activity_embed, sponsorship_gift,
                    banner_placement, display_case_placement,
                    coach_social_count, trial_activity_count, activity_embed_count,
                    sponsorship_gift_details, banner_placement_quantity, display_case_placement_quantity,
                    remarks, plan, check_in_photo, created_by, owner, created_at, lat, lng
                FROM customers 
                {where_clause}
                LIMIT 1
            """
            cursor.execute(sql, params)
            customer = cursor.fetchone()

            if not customer:
                cursor.close()
                conn.close()
                return create_wrapped_response(
                    data=None,
                    message="客户不存在",
                    status=404
                )

            # 处理合同期限
            contract_period = None
            if customer['contract_start_month'] or customer['contract_end_month']:
                contract_period = {
                    "startMonth": customer['contract_start_month'].isoformat() if customer[
                        'contract_start_month'] else None,
                    "endMonth": customer['contract_end_month'].isoformat() if customer['contract_end_month'] else None
                }

            # 处理门店活动
            store_actions = {
                "coachSocial": bool(customer['coach_social']),
                "trialActivity": bool(customer['trial_activity']),
                "activityEmbed": bool(customer['activity_embed']),
                "sponsorshipGift": bool(customer['sponsorship_gift']),
                "bannerPlacement": bool(customer['banner_placement']),
                "displayCasePlacement": bool(customer['display_case_placement'])
            }

            # 处理活动数值
            action_values = {
                "coachSocialCount": customer['coach_social_count'],
                "trialActivityCount": customer['trial_activity_count'],
                "activityEmbedCount": customer['activity_embed_count'],
                "sponsorshipGiftDetails": customer['sponsorship_gift_details'],
                "bannerPlacementQuantity": customer['banner_placement_quantity'],
                "displayCasePlacementQuantity": customer['display_case_placement_quantity']
            }

            formatted_customer = {
                "id": customer['id'],
                "name": customer['name'],
                "isNew": bool(customer['is_new']),
                "ownership": customer['ownership'],
                "region": customer['region'],
                "address": customer['address'],
                "contactPerson": customer['contact_person'],
                "contactMethod": customer['contact_method'],
                "status": customer['status'],
                "contractPeriod": contract_period,
                "attribute": customer['attribute'],
                "type": customer['type'],
                "area": customer['area'],
                "memberCount": customer['member_count'],
                "coachCount": customer['coach_count'],
                "addedCoachCount": customer['added_coach_count'],
                "storeActions": store_actions,
                "actionValues": action_values,
                "remarks": customer['remarks'],
                "plan": customer['plan'],
                "checkInPhoto": customer['check_in_photo'],
                "createdBy": customer['created_by'],
                "owner": customer['owner'],
                "createdAt": customer['created_at'].isoformat() if customer['created_at'] else None,
                "lat": float(customer['lat']) if customer.get('lat') is not None else None,
                "lng": float(customer['lng']) if customer.get('lng') is not None else None
            }

            cursor.close()
            conn.close()

            return create_wrapped_response(
                data=formatted_customer,
                message="获取成功",
                status=200
            )

        # 如果没有指定id，使用分页返回
        # 添加审批状态过滤：只返回已审批通过的客户（audit_status = '1'）或没有审批记录的客户（兼容旧数据）
        # 检查 audit_records 表是否有 customer_id 字段
        cursor.execute("""
            SELECT COUNT(*) as cnt
            FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
            AND TABLE_NAME = 'audit_records'
            AND COLUMN_NAME = 'customer_id'
        """)
        has_customer_id = cursor.fetchone()['cnt'] > 0
        
        # 定义辅助函数：为WHERE子句中的字段添加表别名
        def add_table_alias_to_where_clause(clause):
            """将WHERE子句中的字段名加上表别名c."""
            if not clause:
                return clause
            import re
            # 替换所有字段名，但避免替换已经在表别名中的字段（如 c.region）
            # 使用负向前瞻来避免替换已有别名的字段（前面不是 c. 的字段名）
            result = clause
            # 替换没有表别名的字段名
            result = re.sub(r'(?<!c\.)\bowner\b(?!\s*\.)', 'c.owner', result)
            result = re.sub(r'(?<!c\.)\bregion\b(?!\s*\.)', 'c.region', result)
            result = re.sub(r'(?<!c\.)\bname\b(?!\s*\.)', 'c.name', result)
            result = re.sub(r'(?<!c\.)\bcontact_person\b(?!\s*\.)', 'c.contact_person', result)
            result = re.sub(r'(?<!c\.)\bcontact_method\b(?!\s*\.)', 'c.contact_method', result)
            result = re.sub(r'(?<!c\.)\baddress\b(?!\s*\.)', 'c.address', result)
            result = re.sub(r'(?<!c\.)\battribute\b(?!\s*\.)', 'c.attribute', result)
            result = re.sub(r'(?<!c\.)\btype\b(?!\s*\.)', 'c.type', result)
            result = re.sub(r'(?<!c\.)\bstatus\b(?!\s*\.)', 'c.status', result)
            result = re.sub(r'(?<!c\.)\bownership\b(?!\s*\.)', 'c.ownership', result)
            result = re.sub(r'(?<!c\.)\bcreated_by\b(?!\s*\.)', 'c.created_by', result)
            result = re.sub(r'(?<!c\.)\bid\b(?!\s*\.)', 'c.id', result)
            return result
        
        if has_customer_id:
            # 如果有 customer_id 字段，使用 LEFT JOIN 关联审批记录
            audit_join = """
                LEFT JOIN audit_records ar ON c.id = ar.customer_id
            """
            # 只返回已审批通过的客户（audit_status = '1'），或者没有审批记录的客户（兼容旧数据，但需要确保是旧数据）
            # 注意：对于新创建的客户，如果没有审批记录，不应该显示（因为新客户都应该有审批记录）
            # 所以这里只返回 audit_status = '1' 的客户
            audit_where = " AND (ar.audit_status = '1' OR (ar.audit_status IS NULL AND c.created_at < DATE_SUB(NOW(), INTERVAL 1 DAY)))"
            table_alias = "c"
        else:
            # 如果没有 customer_id 字段，使用 name 和 user_id 关联（兼容旧数据）
            audit_join = """
                LEFT JOIN audit_records ar ON c.name = ar.name AND c.owner = ar.user_id
            """
            # 只返回已审批通过的客户（audit_status = '1'），或者没有审批记录的客户（兼容旧数据）
            audit_where = " AND (ar.audit_status = '1' OR (ar.audit_status IS NULL AND c.created_at < DATE_SUB(NOW(), INTERVAL 1 DAY)))"
            table_alias = "c"
        
        # 修改 where_clause，添加表别名（使用辅助函数）
        where_clause_with_alias = add_table_alias_to_where_clause(where_clause) if where_clause else ""
        if where_clause_with_alias:
            where_clause_with_alias = where_clause_with_alias + audit_where
        else:
            where_clause_with_alias = "WHERE 1=1" + audit_where
        
        # 获取总数
        count_sql = f"SELECT COUNT(DISTINCT {table_alias}.id) as total FROM customers {table_alias} {audit_join} {where_clause_with_alias}"
        cursor.execute(count_sql, params)
        total = cursor.fetchone()['total']

        # 计算分页
        offset = (page - 1) * pageSize

        # 获取数据
        sql = f"""
            SELECT DISTINCT
                {table_alias}.id, {table_alias}.name, {table_alias}.is_new, {table_alias}.ownership, {table_alias}.region, 
                {table_alias}.address, {table_alias}.contact_person, {table_alias}.contact_method,
                {table_alias}.status, {table_alias}.contract_start_month, {table_alias}.contract_end_month, 
                {table_alias}.attribute, {table_alias}.type, {table_alias}.area,
                {table_alias}.member_count, {table_alias}.coach_count, {table_alias}.added_coach_count,
                {table_alias}.coach_social, {table_alias}.trial_activity, {table_alias}.activity_embed, {table_alias}.sponsorship_gift,
                {table_alias}.banner_placement, {table_alias}.display_case_placement,
                {table_alias}.coach_social_count, {table_alias}.trial_activity_count, {table_alias}.activity_embed_count,
                {table_alias}.sponsorship_gift_details, {table_alias}.banner_placement_quantity, {table_alias}.display_case_placement_quantity,
                {table_alias}.remarks, {table_alias}.plan, {table_alias}.check_in_photo, {table_alias}.created_by, 
                {table_alias}.owner, {table_alias}.created_at, {table_alias}.lat, {table_alias}.lng,
                {table_alias}.`省`, {table_alias}.`市`, {table_alias}.`区`
            FROM customers {table_alias}
            {audit_join}
            {where_clause_with_alias}
            ORDER BY 
                CONVERT({table_alias}.`省` USING gbk) ASC,
                CONVERT({table_alias}.`市` USING gbk) ASC,
                CONVERT({table_alias}.`区` USING gbk) ASC,
                {table_alias}.created_at DESC 
            LIMIT %s OFFSET %s
        """
        cursor.execute(sql, params + [pageSize, offset])
        customers = cursor.fetchall()

        # 格式化数据
        formatted_customers = []
        for customer in customers:
            # 处理合同期限
            contract_period = None
            if customer['contract_start_month'] or customer['contract_end_month']:
                contract_period = {
                    "startMonth": customer['contract_start_month'].isoformat() if customer[
                        'contract_start_month'] else None,
                    "endMonth": customer['contract_end_month'].isoformat() if customer['contract_end_month'] else None
                }

            # 处理门店活动
            store_actions = {
                "coachSocial": bool(customer['coach_social']),
                "trialActivity": bool(customer['trial_activity']),
                "activityEmbed": bool(customer['activity_embed']),
                "sponsorshipGift": bool(customer['sponsorship_gift']),
                "bannerPlacement": bool(customer['banner_placement']),
                "displayCasePlacement": bool(customer['display_case_placement'])
            }

            # 处理活动数值
            action_values = {
                "coachSocialCount": customer['coach_social_count'],
                "trialActivityCount": customer['trial_activity_count'],
                "activityEmbedCount": customer['activity_embed_count'],
                "sponsorshipGiftDetails": customer['sponsorship_gift_details'],
                "bannerPlacementQuantity": customer['banner_placement_quantity'],
                "displayCasePlacementQuantity": customer['display_case_placement_quantity']
            }

            formatted_customer = {
                "id": customer['id'],
                "name": customer['name'],
                "isNew": bool(customer['is_new']),
                "ownership": customer['ownership'],
                "region": customer['region'],
                "address": customer['address'],
                "contactPerson": customer['contact_person'],
                "contactMethod": customer['contact_method'],
                "status": customer['status'],
                "contractPeriod": contract_period,
                "attribute": customer['attribute'],
                "type": customer['type'],
                "area": customer['area'],
                "memberCount": customer['member_count'],
                "coachCount": customer['coach_count'],
                "addedCoachCount": customer['added_coach_count'],
                "storeActions": store_actions,
                "actionValues": action_values,
                "remarks": customer['remarks'],
                "plan": customer['plan'],
                "checkInPhoto": customer['check_in_photo'],
                "createdBy": customer['created_by'],
                "owner": customer['owner'],
                "createdAt": customer['created_at'].isoformat() if customer['created_at'] else None,
                "lat": float(customer['lat']) if customer.get('lat') is not None else None,
                "lng": float(customer['lng']) if customer.get('lng') is not None else None
            }
            formatted_customers.append(formatted_customer)

        cursor.close()
        conn.close()

        return create_paginated_response(
            data=formatted_customers,
            total=total,
            page=page,
            pageSize=pageSize,
            message="获取成功"
        )
    except Exception as e:
        import traceback
        error_msg = f"获取客户列表失败: {str(e)}"
        print(f"Error in api_get_customers_list: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


# 1.2 获取客户详情
@app.get("/customers/{customer_id}", response_model=WrappedApiResponse)
async def get_customer_detail(customer_id: int, user_id: int = Depends(get_user_id)):
    """
    获取客户详情接口
    文档来源: 千人千店客户api接口.md - 1.2 获取客户详情
    操作表: customers
    功能: 根据客户ID获取客户详情
    参数: customer_id(客户ID)
    返回: 客户详细信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查用户权限（管理员可以查看所有客户，普通用户只能查看自己的客户）
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 构建查询条件：管理员可以查看所有客户，普通用户只能查看自己的客户或公海客户（owner=0）
        # 注意：owner=0 的客户是公海客户，所有用户都可以查看
        if current_user['role'] == 'admin':
            where_clause = "c.id = %s"
            params = (customer_id,)
        else:
            where_clause = "c.id = %s AND (c.owner = %s OR c.owner = 0)"
            params = (customer_id, user_id)

        # 关联 users 表获取创建人信息和头像
        cursor.execute(f"""
            SELECT 
                c.id, c.name, c.is_new, c.ownership, c.region, c.address, c.contact_person, c.contact_method,
                c.status, c.contract_start_month, c.contract_end_month, c.attribute, c.type, c.area,
                c.member_count, c.coach_count, c.added_coach_count,
                c.coach_social, c.trial_activity, c.activity_embed, c.sponsorship_gift,
                c.banner_placement, c.display_case_placement,
                c.coach_social_count, c.trial_activity_count, c.activity_embed_count,
                c.sponsorship_gift_details, c.banner_placement_quantity, c.display_case_placement_quantity,
                c.remarks, c.plan, c.check_in_photo, c.created_by, c.owner, c.created_at, c.lat, c.lng,
                u.name as creator_name, u.avatar as creator_avatar
            FROM customers c
            LEFT JOIN users u ON c.owner = u.id
            WHERE {where_clause}
        """, params)

        customer = cursor.fetchone()

        if not customer:
            raise HTTPException(status_code=404, detail="客户不存在")

        # 处理合同期限
        contract_period = None
        if customer['contract_start_month'] or customer['contract_end_month']:
            contract_period = {
                "startMonth": customer['contract_start_month'].strftime('%Y-%m-%d') if customer[
                    'contract_start_month'] else None,
                "endMonth": customer['contract_end_month'].strftime('%Y-%m-%d') if customer[
                    'contract_end_month'] else None
            }

        # 处理门店活动
        store_actions = {
            "coachSocial": bool(customer['coach_social']),
            "trialActivity": bool(customer['trial_activity']),
            "activityEmbed": bool(customer['activity_embed']),
            "sponsorshipGift": bool(customer['sponsorship_gift']),
            "bannerPlacement": bool(customer['banner_placement']),
            "displayCasePlacement": bool(customer['display_case_placement'])
        }

        # 处理活动数值
        action_values = {
            "coachSocialCount": customer['coach_social_count'] or 0,
            "trialActivityCount": customer['trial_activity_count'] or 0,
            "activityEmbedCount": customer['activity_embed_count'] or 0,
            "sponsorshipGiftDetails": customer['sponsorship_gift_details'] or "",
            "bannerPlacementQuantity": customer['banner_placement_quantity'] or 0,
            "displayCasePlacementQuantity": customer['display_case_placement_quantity'] or 0
        }

        # 获取创建人信息（优先使用 users 表，如果没有则使用 customers 表的 created_by 字段）
        created_by_name = customer['creator_name'] or customer['created_by'] or ""
        avatar_url = customer['creator_avatar'] or ""

        customer_data = {
            "id": str(customer['id']),
            "name": customer['name'] or "",
            "isNew": bool(customer['is_new']),
            "ownership": customer['ownership'] or "",
            "region": customer['region'] or "",
            "address": customer['address'] or "",
            "avatar": avatar_url,
            "createdBy": created_by_name,
            "createdAt": customer['created_at'].strftime('%Y-%m-%d') if customer['created_at'] else None,
            "contactPerson": customer['contact_person'] or "",
            "contactWay": customer['contact_method'] or "",
            "status": customer['status'] or "",
            "contractPeriod": contract_period,
            "attribute": customer['attribute'] or "",
            "type": customer['type'] or "",
            "area": customer['area'] or "",
            "memberCount": customer['member_count'] or 0,
            "coachCount": customer['coach_count'] or 0,
            "addedCoachCount": customer['added_coach_count'] or 0,
            "storeActions": store_actions,
            "actionValues": action_values,
            "remarks": customer['remarks'] or "",
            "plan": customer['plan'] or "",
            "checkInPhoto": customer['check_in_photo'] or ""
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=customer_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取客户详情失败: {str(e)}")


# 1.3 创建客户
@app.post("/customers", response_model=WrappedApiResponse)
async def create_customer_new(request: CustomerCreateRequest, user_id: int = Depends(get_user_id)):
    """
    创建客户接口
    文档来源: 千人千店客户api接口.md - 1.3 创建客户
    操作表: customers
    功能: 创建新的客户记录
    参数: 客户完整信息
    返回: 创建的客户信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 处理合同期限
        contract_start = None
        contract_end = None
        if request.contractPeriod:
            if request.contractPeriod.get('startMonth'):
                contract_start = datetime.fromisoformat(
                    request.contractPeriod['startMonth'].replace('Z', '+00:00')).date()
            if request.contractPeriod.get('endMonth'):
                contract_end = datetime.fromisoformat(request.contractPeriod['endMonth'].replace('Z', '+00:00')).date()

        # 处理门店活动
        store_actions = request.storeActions or {}
        action_values = request.actionValues or {}

        # 处理region字段：将 / 替换为空格，统一格式
        region_value = request.region
        if region_value:
            region_value = region_value.replace('/', ' ').strip()

        # 自动获取经纬度：如果地址有值但经纬度为空，则根据地址自动获取
        lat = request.lat
        lng = request.lng
        if request.address and (lat is None or lng is None):
            location = get_lat_lng_by_address(request.address)
            if location:
                lat = location.get('lat')
                lng = location.get('lng')
                print(f"自动获取地址 '{request.address}' 的经纬度：lat={lat}, lng={lng}")

        cursor.execute("""
            INSERT INTO customers (
                name, is_new, ownership, region, address, contact_person, contact_method,
                status, contract_start_month, contract_end_month, attribute, type, area,
                member_count, coach_count, added_coach_count,
                coach_social, trial_activity, activity_embed, sponsorship_gift,
                banner_placement, display_case_placement,
                coach_social_count, trial_activity_count, activity_embed_count,
                sponsorship_gift_details, banner_placement_quantity, display_case_placement_quantity,
                remarks, plan, check_in_photo, created_by, owner, lat, lng
            ) VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
            )
        """, (
            request.name, False, request.ownership, region_value, request.address,
            request.contactPerson, request.contactMethod, request.status, contract_start, contract_end,
            request.attribute, request.type, request.area, request.memberCount, request.coachCount,
            request.addedCoachCount,
            store_actions.get('coachSocial', False), store_actions.get('trialActivity', False),
            store_actions.get('activityEmbed', False), store_actions.get('sponsorshipGift', False),
            store_actions.get('bannerPlacement', False), store_actions.get('displayCasePlacement', False),
            action_values.get('coachSocialCount', 0), action_values.get('trialActivityCount', 0),
            action_values.get('activityEmbedCount', 0), action_values.get('sponsorshipGiftDetails', ''),
            action_values.get('bannerPlacementQuantity', 0), action_values.get('displayCasePlacementQuantity', 0),
            request.remarks, request.plan, request.checkInPhoto, request.createdBy or DEFAULT_CREATOR, user_id,
            lat, lng
        ))

        customer_id = cursor.lastrowid
        
        # 查询用户的上级（manager_id）
        cursor.execute("SELECT manager_id FROM users WHERE id = %s", (user_id,))
        user_info = cursor.fetchone()
        manager_id = user_info['manager_id'] if user_info and user_info.get('manager_id') else None
        
        # 创建审批记录
        if manager_id:
            # 如果有上级，创建待审批记录
            try:
                # 检查 audit_records 表是否有 customer_id 字段
                cursor.execute("""
                    SELECT COUNT(*) as cnt
                    FROM information_schema.COLUMNS
                    WHERE TABLE_SCHEMA = DATABASE()
                    AND TABLE_NAME = 'audit_records'
                    AND COLUMN_NAME = 'customer_id'
                """)
                has_customer_id = cursor.fetchone()['cnt'] > 0
                
                if has_customer_id:
                    # 如果有 customer_id 字段，使用新结构
                    cursor.execute("""
                        INSERT INTO audit_records (
                            customer_id, user_id, name, status, type, address, region,
                            attribute, category, audit_status
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, %s, '0'
                        )
                    """, (
                        customer_id, user_id, request.name, request.status, request.type,
                        request.address, region_value, request.attribute, request.type or None
                    ))
                else:
                    # 如果没有 customer_id 字段，使用旧结构
                    cursor.execute("""
                        INSERT INTO audit_records (
                            user_id, name, status, type, address, region,
                            attribute, category, audit_status
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, '0'
                        )
                    """, (
                        user_id, request.name, request.status, request.type,
                        request.address, region_value, request.attribute, request.type or None
                    ))
            except Exception as e:
                print(f"创建审批记录失败: {str(e)}")
                # 即使创建审批记录失败，也不影响客户创建
        else:
            # 如果没有上级，直接创建已通过的审批记录（用于后续查询）
            try:
                cursor.execute("""
                    SELECT COUNT(*) as cnt
                    FROM information_schema.COLUMNS
                    WHERE TABLE_SCHEMA = DATABASE()
                    AND TABLE_NAME = 'audit_records'
                    AND COLUMN_NAME = 'customer_id'
                """)
                has_customer_id = cursor.fetchone()['cnt'] > 0
                
                if has_customer_id:
                    cursor.execute("""
                        INSERT INTO audit_records (
                            customer_id, user_id, name, status, type, address, region,
                            attribute, category, audit_status, audit_user_id, audit_time
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, %s, '1', %s, NOW()
                        )
                    """, (
                        customer_id, user_id, request.name, request.status, request.type,
                        request.address, region_value, request.attribute, request.type or None, user_id
                    ))
                else:
                    cursor.execute("""
                        INSERT INTO audit_records (
                            user_id, name, status, type, address, region,
                            attribute, category, audit_status, audit_user_id, audit_time
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, '1', %s, NOW()
                        )
                    """, (
                        user_id, request.name, request.status, request.type,
                        request.address, region_value, request.attribute, request.type or None, user_id
                    ))
            except Exception as e:
                print(f"创建审批记录失败: {str(e)}")
        
        conn.commit()

        # 获取创建的客户信息
        cursor.execute("""
            SELECT 
                id, name, is_new, ownership, region, address, contact_person, contact_method,
                status, contract_start_month, contract_end_month, attribute, type, area,
                member_count, coach_count, added_coach_count,
                coach_social, trial_activity, activity_embed, sponsorship_gift,
                banner_placement, display_case_placement,
                coach_social_count, trial_activity_count, activity_embed_count,
                sponsorship_gift_details, banner_placement_quantity, display_case_placement_quantity,
                remarks, plan, check_in_photo, created_by, owner, created_at, lat, lng
            FROM customers 
            WHERE id = %s
        """, (customer_id,))

        customer = cursor.fetchone()

        # 处理合同期限
        contract_period = None
        if customer['contract_start_month'] or customer['contract_end_month']:
            contract_period = {
                "startMonth": customer['contract_start_month'].isoformat() if customer[
                    'contract_start_month'] else None,
                "endMonth": customer['contract_end_month'].isoformat() if customer['contract_end_month'] else None
            }

        # 处理门店活动
        store_actions = {
            "coachSocial": bool(customer['coach_social']),
            "trialActivity": bool(customer['trial_activity']),
            "activityEmbed": bool(customer['activity_embed']),
            "sponsorshipGift": bool(customer['sponsorship_gift']),
            "bannerPlacement": bool(customer['banner_placement']),
            "displayCasePlacement": bool(customer['display_case_placement'])
        }

        # 处理活动数值
        action_values = {
            "coachSocialCount": customer['coach_social_count'],
            "trialActivityCount": customer['trial_activity_count'],
            "activityEmbedCount": customer['activity_embed_count'],
            "sponsorshipGiftDetails": customer['sponsorship_gift_details'],
            "bannerPlacementQuantity": customer['banner_placement_quantity'],
            "displayCasePlacementQuantity": customer['display_case_placement_quantity']
        }

        customer_data = {
            "id": customer['id'],
            "name": customer['name'],
            "isNew": bool(customer['is_new']),
            "ownership": customer['ownership'],
            "region": customer['region'],
            "address": customer['address'],
            "contactPerson": customer['contact_person'],
            "contactMethod": customer['contact_method'],
            "status": customer['status'],
            "contractPeriod": contract_period,
            "attribute": customer['attribute'],
            "type": customer['type'],
            "area": customer['area'],
            "memberCount": customer['member_count'],
            "coachCount": customer['coach_count'],
            "addedCoachCount": customer['added_coach_count'],
            "storeActions": store_actions,
            "actionValues": action_values,
            "remarks": customer['remarks'],
            "plan": customer['plan'],
            "checkInPhoto": customer['check_in_photo'],
            "createdBy": customer['created_by'],
            "owner": customer['owner'],
            "createdAt": customer['created_at'].isoformat() if customer['created_at'] else None,
            "lat": float(customer['lat']) if customer.get('lat') is not None else None,
            "lng": float(customer['lng']) if customer.get('lng') is not None else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=customer_data,
            message="创建成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建客户失败: {str(e)}")


# 1.4 更新客户
@app.put("/customers/{customer_id}", response_model=WrappedApiResponse)
async def update_customer_new(customer_id: int, request: CustomerUpdateRequest, user_id: int = Depends(get_user_id)):
    """
    更新客户接口
    文档来源: 千人千店客户api接口.md - 1.4 更新客户
    操作表: customers
    功能: 更新客户信息
    参数: customer_id(客户ID), 客户更新信息
    返回: 更新后的客户信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查客户是否存在
        cursor.execute("SELECT id, owner FROM customers WHERE id = %s", (customer_id,))
        customer = cursor.fetchone()
        if not customer:
            raise HTTPException(status_code=404, detail="客户不存在")

        # 检查权限：管理员可以更新任何客户，普通用户只能更新自己的客户或公海客户（owner为NULL或0）
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 获取owner值，处理NULL情况（公海客户的owner为NULL或0）
        owner_value = customer.get('owner')
        # 允许管理员更新任何客户，普通用户只能更新自己的客户或公海客户（owner为NULL或0）
        if current_user['role'] != 'admin' and owner_value != user_id and owner_value not in (None, 0):
            raise HTTPException(status_code=403, detail="无权限更新该客户")

        # 构建更新字段
        update_fields = []
        update_values = []

        if request.name is not None:
            update_fields.append("name = %s")
            update_values.append(request.name)

        if request.ownership is not None:
            update_fields.append("ownership = %s")
            update_values.append(request.ownership)

        if request.region is not None:
            # 处理region字段：将 / 替换为空格，统一格式
            region_value = request.region.replace('/', ' ').strip() if request.region else None
            update_fields.append("region = %s")
            update_values.append(region_value)

        if request.address is not None:
            update_fields.append("address = %s")
            update_values.append(request.address)
            # 如果更新了地址，且没有提供经纬度，则自动获取经纬度
            if (request.lat is None or request.lng is None):
                location = get_lat_lng_by_address(request.address)
                if location:
                    # 自动添加经纬度更新
                    if "lat = %s" not in update_fields:
                        update_fields.append("lat = %s")
                        update_values.append(location.get('lat'))
                    if "lng = %s" not in update_fields:
                        update_fields.append("lng = %s")
                        update_values.append(location.get('lng'))
                    print(
                        f"更新地址 '{request.address}' 时自动获取经纬度：lat={location.get('lat')}, lng={location.get('lng')}")

        if request.contactPerson is not None:
            update_fields.append("contact_person = %s")
            update_values.append(request.contactPerson)

        if request.contactMethod is not None:
            update_fields.append("contact_method = %s")
            update_values.append(request.contactMethod)

        if request.status is not None:
            update_fields.append("status = %s")
            update_values.append(request.status)

        if request.attribute is not None:
            update_fields.append("attribute = %s")
            update_values.append(request.attribute)

        if request.type is not None:
            update_fields.append("type = %s")
            update_values.append(request.type)

        if request.area is not None:
            update_fields.append("area = %s")
            update_values.append(request.area)

        if request.memberCount is not None:
            update_fields.append("member_count = %s")
            update_values.append(request.memberCount)

        if request.coachCount is not None:
            update_fields.append("coach_count = %s")
            update_values.append(request.coachCount)

        if request.addedCoachCount is not None:
            update_fields.append("added_coach_count = %s")
            update_values.append(request.addedCoachCount)

        if request.remarks is not None:
            update_fields.append("remarks = %s")
            update_values.append(request.remarks)

        if request.plan is not None:
            update_fields.append("plan = %s")
            update_values.append(request.plan)

        if request.checkInPhoto is not None:
            update_fields.append("check_in_photo = %s")
            update_values.append(request.checkInPhoto)

        if request.createdBy is not None:
            update_fields.append("created_by = %s")
            update_values.append(request.createdBy)

        if request.lat is not None:
            update_fields.append("lat = %s")
            update_values.append(request.lat)

        if request.lng is not None:
            update_fields.append("lng = %s")
            update_values.append(request.lng)

        if request.owner is not None:
            update_fields.append("owner = %s")
            update_fields.append("owner_updated_at = NOW()")  # 同时更新owner字段更新时间
            # 处理空字符串，转换为None（数据库中的NULL）
            if request.owner == "":
                update_values.append(None)
            else:
                update_values.append(request.owner)

        # 处理合同期限
        if request.contractPeriod is not None:
            if request.contractPeriod.get('startMonth'):
                contract_start = datetime.fromisoformat(
                    request.contractPeriod['startMonth'].replace('Z', '+00:00')).date()
                update_fields.append("contract_start_month = %s")
                update_values.append(contract_start)
            if request.contractPeriod.get('endMonth'):
                contract_end = datetime.fromisoformat(request.contractPeriod['endMonth'].replace('Z', '+00:00')).date()
                update_fields.append("contract_end_month = %s")
                update_values.append(contract_end)

        # 处理门店活动
        if request.storeActions is not None:
            store_actions = request.storeActions
            if 'coachSocial' in store_actions:
                update_fields.append("coach_social = %s")
                update_values.append(store_actions['coachSocial'])
            if 'trialActivity' in store_actions:
                update_fields.append("trial_activity = %s")
                update_values.append(store_actions['trialActivity'])
            if 'activityEmbed' in store_actions:
                update_fields.append("activity_embed = %s")
                update_values.append(store_actions['activityEmbed'])
            if 'sponsorshipGift' in store_actions:
                update_fields.append("sponsorship_gift = %s")
                update_values.append(store_actions['sponsorshipGift'])
            if 'bannerPlacement' in store_actions:
                update_fields.append("banner_placement = %s")
                update_values.append(store_actions['bannerPlacement'])
            if 'displayCasePlacement' in store_actions:
                update_fields.append("display_case_placement = %s")
                update_values.append(store_actions['displayCasePlacement'])

        # 处理活动数值
        if request.actionValues is not None:
            action_values = request.actionValues
            if 'coachSocialCount' in action_values:
                update_fields.append("coach_social_count = %s")
                update_values.append(action_values['coachSocialCount'])
            if 'trialActivityCount' in action_values:
                update_fields.append("trial_activity_count = %s")
                update_values.append(action_values['trialActivityCount'])
            if 'activityEmbedCount' in action_values:
                update_fields.append("activity_embed_count = %s")
                update_values.append(action_values['activityEmbedCount'])
            if 'sponsorshipGiftDetails' in action_values:
                update_fields.append("sponsorship_gift_details = %s")
                update_values.append(action_values['sponsorshipGiftDetails'])
            if 'bannerPlacementQuantity' in action_values:
                update_fields.append("banner_placement_quantity = %s")
                update_values.append(action_values['bannerPlacementQuantity'])
            if 'displayCasePlacementQuantity' in action_values:
                update_fields.append("display_case_placement_quantity = %s")
                update_values.append(action_values['displayCasePlacementQuantity'])

        if update_fields:
            update_values.append(customer_id)
            cursor.execute(f"UPDATE customers SET {', '.join(update_fields)} WHERE id = %s", update_values)
            conn.commit()

        # 获取更新后的客户信息
        cursor.execute("""
            SELECT 
                id, name, is_new, ownership, region, address, contact_person, contact_method,
                status, contract_start_month, contract_end_month, attribute, type, area,
                member_count, coach_count, added_coach_count,
                coach_social, trial_activity, activity_embed, sponsorship_gift,
                banner_placement, display_case_placement,
                coach_social_count, trial_activity_count, activity_embed_count,
                sponsorship_gift_details, banner_placement_quantity, display_case_placement_quantity,
                remarks, plan, check_in_photo, created_by, owner, created_at, lat, lng
            FROM customers 
            WHERE id = %s
        """, (customer_id,))

        customer = cursor.fetchone()

        # 处理合同期限
        contract_period = None
        if customer['contract_start_month'] or customer['contract_end_month']:
            contract_period = {
                "startMonth": customer['contract_start_month'].isoformat() if customer[
                    'contract_start_month'] else None,
                "endMonth": customer['contract_end_month'].isoformat() if customer['contract_end_month'] else None
            }

        # 处理门店活动
        store_actions = {
            "coachSocial": bool(customer['coach_social']),
            "trialActivity": bool(customer['trial_activity']),
            "activityEmbed": bool(customer['activity_embed']),
            "sponsorshipGift": bool(customer['sponsorship_gift']),
            "bannerPlacement": bool(customer['banner_placement']),
            "displayCasePlacement": bool(customer['display_case_placement'])
        }

        # 处理活动数值
        action_values = {
            "coachSocialCount": customer['coach_social_count'],
            "trialActivityCount": customer['trial_activity_count'],
            "activityEmbedCount": customer['activity_embed_count'],
            "sponsorshipGiftDetails": customer['sponsorship_gift_details'],
            "bannerPlacementQuantity": customer['banner_placement_quantity'],
            "displayCasePlacementQuantity": customer['display_case_placement_quantity']
        }

        customer_data = {
            "id": customer['id'],
            "name": customer['name'],
            "isNew": bool(customer['is_new']),
            "ownership": customer['ownership'],
            "region": customer['region'],
            "address": customer['address'],
            "contactPerson": customer['contact_person'],
            "contactMethod": customer['contact_method'],
            "status": customer['status'],
            "contractPeriod": contract_period,
            "attribute": customer['attribute'],
            "type": customer['type'],
            "area": customer['area'],
            "memberCount": customer['member_count'],
            "coachCount": customer['coach_count'],
            "addedCoachCount": customer['added_coach_count'],
            "storeActions": store_actions,
            "actionValues": action_values,
            "remarks": customer['remarks'],
            "plan": customer['plan'],
            "checkInPhoto": customer['check_in_photo'],
            "createdBy": customer['created_by'],
            "owner": customer['owner'],
            "createdAt": customer['created_at'].isoformat() if customer['created_at'] else None,
            "lat": float(customer['lat']) if customer.get('lat') is not None else None,
            "lng": float(customer['lng']) if customer.get('lng') is not None else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=customer_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新客户失败: {str(e)}")


# 1.5 删除客户
@app.delete("/customers/{customer_id}", response_model=WrappedApiResponse)
async def delete_customer_new(customer_id: int, user_id: int = Depends(get_user_id)):
    """
    删除客户接口
    文档来源: 千人千店客户api接口.md - 1.5 删除客户
    操作表: customers
    功能: 删除指定客户
    参数: customer_id(客户ID)
    返回: 删除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 检查客户是否存在且属于当前用户
        cursor.execute("SELECT id FROM customers WHERE id = %s AND owner = %s", (customer_id, user_id))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="客户不存在或无权限")

        # 删除客户
        cursor.execute("DELETE FROM customers WHERE id = %s", (customer_id,))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="删除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除客户失败: {str(e)}")


# 1.6 从我的客户中移除
@app.post("/customers/{customer_id}/remove-from-my", response_model=WrappedApiResponse)
async def remove_customer_from_my(customer_id: int, user_id: int = Depends(get_user_id)):
    """
    从我的客户中移除接口
    文档来源: 千人千店客户api接口.md - 1.6 从我的客户中移除
    操作表: customers, customer_removals
    功能: 将客户从指定用户的所有者列表中移除
    参数: customer_id(客户ID), user_id(用户ID)
    返回: 移除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 检查客户是否存在
        cursor.execute("SELECT id, owner FROM customers WHERE id = %s", (customer_id,))
        customer = cursor.fetchone()
        if not customer:
            raise HTTPException(status_code=404, detail="客户不存在")

        # 检查用户是否有权限移除该客户
        if customer[1] != user_id:
            raise HTTPException(status_code=403, detail="无权限移除该客户")

        # 记录移除操作
        cursor.execute("""
            INSERT INTO customer_removals (customer_id, user_id, reason) 
            VALUES (%s, %s, %s)
        """, (customer_id, user_id, "用户主动移除"))

        # 更新客户的所有者
        cursor.execute("UPDATE customers SET owner = NULL, owner_updated_at = NOW() WHERE id = %s", (customer_id,))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="移除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"移除客户失败: {str(e)}")


# ==================== 新增排班计划API接口 ====================

# 2.1 获取日程列表
@app.get("/schedules", response_model=WrappedApiResponse)
async def get_schedules_new(user_id: int = Depends(get_user_id)):
    """
    获取日程列表接口
    文档来源: 千人千店客户api接口.md - 2.1 获取日程列表
    操作表: schedules
    功能: 获取所有日程列表
    参数: 无
    返回: 日程列表数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        cursor.execute("""
            SELECT 
                s.id, s.customer_id, s.customer_name, s.title, s.description, 
                s.start_time, s.end_time, s.status, s.created_at, s.updated_at
            FROM schedules s
            LEFT JOIN customers c ON s.customer_id = c.id
            WHERE c.owner = %s OR s.created_by = %s
            ORDER BY s.created_at DESC
        """, (user_id, user_id))

        schedules = cursor.fetchall()

        # 格式化数据
        formatted_schedules = []
        for schedule in schedules:
            formatted_schedule = {
                "id": str(schedule['id']),
                "customerId": str(schedule['customer_id']) if schedule['customer_id'] else None,
                "customerName": schedule['customer_name'],
                "title": schedule['title'],
                "description": schedule['description'],
                "startTime": schedule['start_time'].isoformat() if schedule['start_time'] else None,
                "endTime": schedule['end_time'].isoformat() if schedule['end_time'] else None,
                "status": schedule['status'],
                "createdAt": schedule['created_at'].isoformat() if schedule['created_at'] else None,
                "updatedAt": schedule['updated_at'].isoformat() if schedule['updated_at'] else None
            }
            formatted_schedules.append(formatted_schedule)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=formatted_schedules,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取日程列表失败: {str(e)}")


# 2.2 获取日程详情
@app.get("/store-schedule-plan/{store_schedule_plan_id}", response_model=WrappedApiResponse)
async def get_store_schedule_plan_detail(store_schedule_plan_id: int, user_id: int = Depends(get_user_id)):
    """
    获取门店计划详情接口
    文档来源: 千人千店客户api接口.md - 2.2 获取日程详情
    操作表: store_schedule_plans
    功能: 根据门店计划ID获取门店计划详情
    参数: store_schedule_plan_id(门店计划ID)
    返回: 门店计划详细信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        cursor.execute("""
            SELECT 
                store_schedule_plan_id, store_id, name, address, status, 
                visited_status, user_id, schedule_date, type, region, remark
            FROM store_schedule_plans 
            WHERE store_schedule_plan_id = %s
        """, (store_schedule_plan_id,))

        plan = cursor.fetchone()

        if not plan:
            raise HTTPException(status_code=404, detail="门店计划不存在")

        # 验证权限：只有管理员或日程所有者可以查看日程详情
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以查看任何日程，普通用户只能查看自己的日程
        if current_user['role'] != 'admin' and user_id != plan['user_id']:
            raise HTTPException(status_code=403, detail="无权限查看该日程详情")

        plan_data = {
            "storeSchedulePlanId": plan['store_schedule_plan_id'],
            "id": plan['store_id'],
            "name": plan['name'],
            "address": plan['address'],
            "status": plan['status'],
            "visitedstatus": bool(plan['visited_status']),
            "userId": plan['user_id'],
            "scheduleDate": plan['schedule_date'].isoformat() if plan['schedule_date'] else None,
            "type": plan['type'],
            "region": plan['region'],
            "remark": plan['remark']
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=plan_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取门店计划详情失败: {str(e)}")


# 2.3 创建日程
@app.post("/schedules", response_model=WrappedApiResponse)
async def create_schedule_new(request: ScheduleCreateRequest, user_id: int = Depends(get_user_id)):
    """
    创建日程接口
    文档来源: 千人千店客户api接口.md - 2.3 创建日程
    操作表: schedules
    功能: 创建新的日程安排
    参数: 日程完整信息
    返回: 创建的日程信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 处理时间字段
        start_time = None
        end_time = None

        if request.startTime:
            start_time = datetime.fromisoformat(request.startTime.replace('Z', '+00:00'))
        if request.endTime:
            end_time = datetime.fromisoformat(request.endTime.replace('Z', '+00:00'))

        # 获取客户名称
        customer_name = None
        if request.customerId:
            cursor.execute("SELECT name FROM customers WHERE id = %s", (request.customerId,))
            customer = cursor.fetchone()
            if customer:
                customer_name = customer[0]

        cursor.execute("""
            INSERT INTO schedules (
                customer_id, customer_name, title, description, 
                start_time, end_time, status, created_by
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
        """, (
            request.customerId, customer_name, request.title, request.description,
            start_time, end_time, request.status, user_id
        ))

        schedule_id = cursor.lastrowid
        conn.commit()

        # 获取创建的日程信息
        cursor.execute("""
            SELECT 
                id, customer_id, customer_name, title, description, 
                start_time, end_time, status, created_at, updated_at
            FROM schedules 
            WHERE id = %s
        """, (schedule_id,))

        schedule = cursor.fetchone()

        schedule_data = {
            "id": str(schedule[0]),
            "customerId": str(schedule[1]) if schedule[1] else None,
            "customerName": schedule[2],
            "title": schedule[3],
            "description": schedule[4],
            "startTime": schedule[5].isoformat() if schedule[5] else None,
            "endTime": schedule[6].isoformat() if schedule[6] else None,
            "status": schedule[7],
            "createdAt": schedule[8].isoformat() if schedule[8] else None,
            "updatedAt": schedule[9].isoformat() if schedule[9] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=schedule_data,
            message="创建成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建日程失败: {str(e)}")


# 2.4 更新日程
@app.put("/schedules/{schedule_id}", response_model=WrappedApiResponse)
async def update_schedule_new(schedule_id: int, request: ScheduleUpdateRequest, user_id: int = Depends(get_user_id)):
    """
    更新日程接口
    文档来源: 千人千店客户api接口.md - 2.4 更新日程
    操作表: schedules
    功能: 更新日程信息
    参数: schedule_id(日程ID), 日程更新信息
    返回: 更新后的日程信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查日程是否存在且属于当前用户
        cursor.execute("""
            SELECT s.id FROM schedules s 
            LEFT JOIN customers c ON s.customer_id = c.id 
            WHERE s.id = %s AND (s.created_by = %s OR c.owner = %s)
        """, (schedule_id, user_id, user_id))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="日程不存在或无权限")

        # 构建更新字段
        update_fields = []
        update_values = []

        if request.customerId is not None:
            update_fields.append("customer_id = %s")
            update_values.append(request.customerId)

            # 获取客户名称
            cursor.execute("SELECT name FROM customers WHERE id = %s", (request.customerId,))
            customer = cursor.fetchone()
            if customer:
                update_fields.append("customer_name = %s")
                update_values.append(customer['name'])

        if request.title is not None:
            update_fields.append("title = %s")
            update_values.append(request.title)

        if request.description is not None:
            update_fields.append("description = %s")
            update_values.append(request.description)

        if request.startTime is not None:
            start_time = datetime.fromisoformat(request.startTime.replace('Z', '+00:00'))
            update_fields.append("start_time = %s")
            update_values.append(start_time)

        if request.endTime is not None:
            end_time = datetime.fromisoformat(request.endTime.replace('Z', '+00:00'))
            update_fields.append("end_time = %s")
            update_values.append(end_time)

        if request.status is not None:
            update_fields.append("status = %s")
            update_values.append(request.status)

        if update_fields:
            update_values.append(schedule_id)
            cursor.execute(f"UPDATE schedules SET {', '.join(update_fields)} WHERE id = %s", update_values)
            conn.commit()

        # 获取更新后的日程信息
        cursor.execute("""
            SELECT 
                id, customer_id, customer_name, title, description, 
                start_time, end_time, status, created_at, updated_at
            FROM schedules 
            WHERE id = %s
        """, (schedule_id,))

        schedule = cursor.fetchone()

        schedule_data = {
            "id": str(schedule['id']),
            "customerId": str(schedule['customer_id']) if schedule['customer_id'] else None,
            "customerName": schedule['customer_name'],
            "title": schedule['title'],
            "description": schedule['description'],
            "startTime": schedule['start_time'].isoformat() if schedule['start_time'] else None,
            "endTime": schedule['end_time'].isoformat() if schedule['end_time'] else None,
            "status": schedule['status'],
            "createdAt": schedule['created_at'].isoformat() if schedule['created_at'] else None,
            "updatedAt": schedule['updated_at'].isoformat() if schedule['updated_at'] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=schedule_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新日程失败: {str(e)}")


# 2.5 删除日程
@app.delete("/schedules/{schedule_id}", response_model=WrappedApiResponse)
async def delete_schedule_new(schedule_id: int, user_id: int = Depends(get_user_id)):
    """
    删除日程接口
    文档来源: 千人千店客户api接口.md - 2.5 删除日程
    操作表: schedules
    功能: 删除指定日程
    参数: schedule_id(日程ID)
    返回: 删除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 检查日程是否存在且属于当前用户
        cursor.execute("""
            SELECT s.id FROM schedules s 
            LEFT JOIN customers c ON s.customer_id = c.id 
            WHERE s.id = %s AND (s.created_by = %s OR c.owner = %s)
        """, (schedule_id, user_id, user_id))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="日程不存在或无权限")

        # 删除日程
        cursor.execute("DELETE FROM schedules WHERE id = %s", (schedule_id,))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="删除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除日程失败: {str(e)}")


# 2.6 移除日程
@app.delete("/schedules/plan/{store_schedule_plan_id}", response_model=WrappedApiResponse)
async def remove_schedule_new(store_schedule_plan_id: int, user_id: int = Depends(get_user_id)):
    """
    移除日程接口
    文档来源: 千人千店客户api接口.md - 2.6 移除日程
    操作表: store_schedule_plans
    功能: 根据门店计划ID删除日程
    参数: store_schedule_plan_id(门店计划ID)
    返回: 删除成功消息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查门店计划是否存在并验证权限
        cursor.execute(
            "SELECT store_schedule_plan_id, user_id FROM store_schedule_plans WHERE store_schedule_plan_id = %s",
            (store_schedule_plan_id,))
        plan = cursor.fetchone()
        if not plan:
            raise HTTPException(status_code=404, detail="门店计划不存在")

        # 验证权限：只有管理员或日程所有者可以删除日程
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以删除任何日程，普通用户只能删除自己的日程
        if current_user['role'] != 'admin' and user_id != plan['user_id']:
            raise HTTPException(status_code=403, detail="无权限删除该日程")

        # 删除门店计划
        cursor.execute("DELETE FROM store_schedule_plans WHERE store_schedule_plan_id = %s", (store_schedule_plan_id,))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="删除成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除门店计划失败: {str(e)}")


# 2.7 保存自定义日程
@app.post("/add/custom-schedule", response_model=WrappedApiResponse)
async def save_custom_schedule(request: CustomScheduleRequest, user_id: int = Depends(get_user_id)):
    """
    保存自定义日程接口
    文档来源: 千人千店客户api接口.md - 2.7 保存自定义日程
    操作表: store_schedule_plans
    功能: 保存自定义日程
    参数: 自定义日程完整信息
    返回: 保存的自定义日程信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证必填字段
        if not request.name or not request.name.strip():
            raise HTTPException(status_code=400, detail="门店名称不能为空")

        if not request.scheduleDate:
            raise HTTPException(status_code=400, detail="计划日期不能为空")

        # 处理计划日期
        schedule_date = None
        try:
            if request.scheduleDate:
                schedule_date = datetime.strptime(request.scheduleDate, '%Y-%m-%d').date()
        except ValueError:
            raise HTTPException(status_code=400, detail="计划日期格式错误，应为YYYY-MM-DD格式")

        # 验证权限：只有管理员或用户本人可以创建日程
        cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以为任何用户创建日程，普通用户只能为自己创建日程
        if current_user['role'] != 'admin' and user_id != request.userId:
            raise HTTPException(status_code=403, detail="无权限为该用户创建日程")

        # 转换visitedstatus为字符串（数据库字段是VARCHAR类型）
        visited_status_str = 'visited' if request.visitedstatus else 'not_visited'

        print(
            f"准备插入数据: {request.id}, {request.name}, {request.address}, {request.status}, {visited_status_str}, {request.userId}, {schedule_date}, {request.type}, {request.region}, {request.remark}")

        cursor.execute("""
            INSERT INTO store_schedule_plans (
                store_id, name, address, status, visited_status, 
                user_id, schedule_date, type, region, remark
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """, (
            request.id, request.name, request.address, request.status, visited_status_str,
            request.userId, schedule_date, request.type, request.region, request.remark
        ))

        # 获取插入后的自增ID（必须在commit之前获取）
        store_schedule_plan_id = cursor.lastrowid
        print(f"插入后的自增ID (lastrowid): {store_schedule_plan_id}")

        # 同时向user_visit_records表插入记录（如果该日期还没有记录）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM user_visit_records 
            WHERE user_id = %s AND schedule_date = %s
        """, (request.userId, schedule_date))
        existing_record = cursor.fetchone()

        if existing_record['count'] == 0:
            # 如果该日期还没有记录，则插入
            cursor.execute("""
                INSERT INTO user_visit_records (user_id, schedule_date)
                VALUES (%s, %s)
            """, (request.userId, schedule_date))
            print(f"已向user_visit_records表插入记录: user_id={request.userId}, schedule_date={schedule_date}")

        # 提交事务
        conn.commit()

        print(f"提交事务后，准备查询ID: {store_schedule_plan_id}")

        # 如果 lastrowid 为 0 或 None，尝试通过其他方式获取ID
        if not store_schedule_plan_id or store_schedule_plan_id == 0:
            # 通过查询条件查找刚插入的记录
            cursor.execute("""
                SELECT * FROM store_schedule_plans 
                WHERE store_id = %s AND user_id = %s AND schedule_date = %s AND name = %s
                ORDER BY store_schedule_plan_id DESC
                LIMIT 1
            """, (request.id, request.userId, schedule_date, request.name))
            inserted_plan = cursor.fetchone()
            if inserted_plan:
                store_schedule_plan_id = inserted_plan['store_schedule_plan_id']
                print(f"通过查询获取的ID: {store_schedule_plan_id}")
            else:
                raise HTTPException(status_code=500, detail="保存成功但无法获取插入的数据ID")
        else:
            # 重新查询插入的数据以确保数据完整
            cursor.execute("""
                SELECT * FROM store_schedule_plans 
                WHERE store_schedule_plan_id = %s
            """, (store_schedule_plan_id,))
            inserted_plan = cursor.fetchone()

        if not inserted_plan:
            raise HTTPException(status_code=500, detail="保存成功但无法获取插入的数据")

        # 构建返回数据
        plan_data = {
            "storeSchedulePlanId": inserted_plan['store_schedule_plan_id'],
            "id": inserted_plan['store_id'],
            "name": inserted_plan['name'],
            "address": inserted_plan['address'],
            "status": inserted_plan['status'],
            "visitedstatus": inserted_plan['visited_status'] if isinstance(inserted_plan['visited_status'], bool) else (
                        inserted_plan['visited_status'] == 'visited' or inserted_plan['visited_status'] == True),
            "userId": inserted_plan['user_id'],
            "scheduleDate": inserted_plan['schedule_date'].isoformat() if inserted_plan['schedule_date'] else None,
            "type": inserted_plan['type'],
            "region": inserted_plan['region'],
            "remark": inserted_plan['remark']
        }

        cursor.close()
        conn.close()

        # 直接返回，避免双重嵌套
        return WrappedApiResponse(
            data=plan_data,
            message="保存成功",
            status=200
        )
    except Exception as e:
        print(f"保存自定义日程详细错误: {e}")
        print(f"错误类型: {type(e)}")
        import traceback
        traceback.print_exc()
        raise HTTPException(status_code=500, detail=f"保存自定义日程失败: {str(e)}")


# 2.8 获取门店列表
@app.get("/stores", response_model=WrappedApiResponse)
async def get_stores_new(
        userId: Optional[int] = Query(None, description="用户ID"),
        scheduleDate: Optional[str] = Query(None, description="计划日期，格式：YYYY-MM-DD"),
        user_id: int = Depends(get_user_id)
):
    """
    获取门店列表接口
    文档来源: 千人千店客户api接口.md - 2.8 获取门店列表
    操作表: store_schedule_plans
    功能: 获取门店列表，支持按用户ID和计划日期筛选
    参数: userId(可选，用户ID), scheduleDate(可选，计划日期，格式：YYYY-MM-DD)
    返回: 门店列表数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建查询条件
        where_conditions = []
        params = []

        # 默认只查询当前用户的数据，除非指定了userId参数
        query_user_id = userId if userId is not None else user_id
        where_conditions.append("ssp.user_id = %s")
        params.append(query_user_id)

        # 如果指定了scheduleDate，添加日期筛选
        if scheduleDate:
            where_conditions.append("ssp.schedule_date = %s")
            params.append(scheduleDate)

        where_clause = " AND ".join(where_conditions)

        # 关联customers表获取客户的最新状态
        cursor.execute(f"""
            SELECT 
                ssp.store_schedule_plan_id, 
                ssp.store_id, 
                COALESCE(c.name, ssp.name) as name, 
                COALESCE(c.address, ssp.address) as address, 
                COALESCE(c.status, ssp.status) as status, 
                ssp.visited_status, 
                ssp.user_id, 
                ssp.schedule_date, 
                ssp.type, 
                COALESCE(c.region, ssp.region) as region, 
                ssp.remark
            FROM store_schedule_plans ssp
            LEFT JOIN customers c ON ssp.store_id = c.id
            WHERE {where_clause}
            ORDER BY ssp.created_at DESC
        """, params)

        stores = cursor.fetchall()

        # 格式化数据
        formatted_stores = []
        for store in stores:
            formatted_store = {
                "storeSchedulePlanId": store['store_schedule_plan_id'],
                "id": store['store_id'],
                "name": store['name'],
                "address": store['address'],
                "status": store['status'],  # 使用customers表中的最新状态，如果没有则使用store_schedule_plans中的状态
                "visitedstatus": bool(store['visited_status']),
                "userId": store['user_id'],
                "scheduleDate": store['schedule_date'].isoformat() if store['schedule_date'] else None,
                "type": store['type'],
                "region": store['region'],
                "remark": store['remark']
            }
            formatted_stores.append(formatted_store)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=formatted_stores,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取门店列表失败: {str(e)}")


# 2.9 获取部门下属用户
@app.get("/department/users", response_model=WrappedApiResponse)
async def get_department_users_new(user_id: int = Depends(get_user_id)):
    """
    获取部门下属用户接口
    文档来源: 千人千店客户api接口.md - 2.9 获取部门下属用户
    操作表: users
    功能: 获取当前用户的下属用户列表（包括用户自己）
    参数: 无（从请求头获取 user_id）
    返回: 部门下属用户列表数据
    说明: 通过 users 表的 manager_id 字段查询下属，manager_id 存储的是上级用户的ID
          用户自己也是自己的下级（自己也是自己的下级）
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 查询下属用户和用户自己：查询 users 表中 manager_id = user_id 的记录，以及用户自己（id = user_id）
        # manager_id 字段存储的是上级用户的ID
        # 用户自己也是自己的下级（自己也是自己的下级）
        cursor.execute("""
            SELECT id, name, avatar, account, role, department, status, created_at, updated_at
            FROM users 
            WHERE manager_id = %s OR id = %s
            ORDER BY id ASC
        """, (user_id, user_id))

        users = cursor.fetchall()

        # 格式化数据（保持原有格式，同时增加更多字段）
        formatted_users = []
        for user in users:
            formatted_user = {
                "text": user['name'],
                "id": user['id'],
                "name": user['name'],
                "avatar": user.get('avatar') or "",
                "account": user.get('account') or "",
                "role": user.get('role') or "",
                "department": user.get('department') or "",
                "status": user.get('status') or "enabled",
                "createdAt": user.get('created_at').strftime("%Y-%m-%d %H:%M:%S") if user.get('created_at') else None,
                "updatedAt": user.get('updated_at').strftime("%Y-%m-%d %H:%M:%S") if user.get('updated_at') else None
            }
            formatted_users.append(formatted_user)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=formatted_users,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取部门下属用户失败: {str(e)}")


# 2.10 获取公告信息
@app.get("/notice/{user_id}", response_model=WrappedApiResponse)
async def get_notice_new(user_id: int, current_user_id: int = Depends(get_user_id)):
    """
    获取公告信息接口
    文档来源: 千人千店客户api接口.md - 2.10 获取公告信息
    操作表: notices
    功能: 根据用户ID获取公告信息
    参数: user_id(用户ID)
    返回: 公告内容
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以查看公告
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以查看任何用户公告，普通用户只能查看自己的公告
        if current_user['role'] != 'admin' and current_user_id != user_id:
            raise HTTPException(status_code=403, detail="无权限查看该用户公告")

        cursor.execute("""
            SELECT content 
            FROM notices 
            WHERE user_id = %s 
            ORDER BY created_at DESC 
            LIMIT 1
        """, (user_id,))

        notice = cursor.fetchone()

        notice_content = notice['content'] if notice and notice['content'] else "暂无公告"

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=notice_content,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取公告信息失败: {str(e)}")


# 2.11 获取日历数据
@app.get("/calendar-data", response_model=WrappedApiResponse)
async def get_calendar_data_new(user_id: int = Depends(get_user_id)):
    """
    获取日历数据接口
    文档来源: 千人千店客户api接口.md - 2.11 获取日历数据
    操作表: schedules, store_schedule_plans
    功能: 获取系统日历的最小和最大日期
    参数: 无
    返回: 日历数据
    """
    try:
        # 计算当前时间前后一年的日期范围
        today = datetime.now().date()
        min_date = (today - timedelta(days=365)).strftime('%Y-%m-%d')  # 一年前
        max_date = (today + timedelta(days=365)).strftime('%Y-%m-%d')  # 一年后

        calendar_data = {
            "minDate": min_date,
            "maxDate": max_date
        }

        return create_wrapped_response(
            data=calendar_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取日历数据失败: {str(e)}")


# ==================== 个人资料API接口 ====================

# 2.1 获取当前用户信息
@app.get("/profile/user-info", response_model=WrappedApiResponse)
async def get_current_user_profile(user_id: int = Depends(get_user_id)):
    """
    获取当前用户信息接口
    文档来源: 千人千店个人.md - 2.1 获取当前用户信息
    操作表: users
    功能: 根据用户ID获取用户详细信息
    参数: user_id(用户ID)
    返回: 用户详细信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        cursor.execute("""
            SELECT 
                id, account, name, role, avatar, phone, email, 
                position, department, status, created_at, updated_at
            FROM users 
            WHERE id = %s
        """, (user_id,))

        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=404, detail="用户不存在")

        user_data = {
            "id": str(user['id']),
            "account": user['account'],
            "name": user['name'],
            "role": user['role'],
            "avatar": user['avatar'],
            "phone": user['phone'],
            "email": user['email'],
            "position": user['role'],  # position 的值等于 role 的值
            "department": user['department'],
            "status": user['status'],
            "createdAt": user['created_at'].strftime('%Y-%m-%d') if user['created_at'] else None,
            "updatedAt": user['updated_at'].strftime('%Y-%m-%d') if user['updated_at'] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")


# 2.2 更新当前用户信息
@app.put("/profile/user-info", response_model=WrappedApiResponse)
async def update_current_user_profile(
        user_id: int = Depends(get_user_id),
        name: Optional[str] = None,
        phone: Optional[str] = None,
        email: Optional[str] = None,
        position: Optional[str] = None,
        department: Optional[str] = None,
        avatar: Optional[str] = None
):
    """
    更新当前用户信息接口
    文档来源: 千人千店个人.md - 2.2 更新当前用户信息
    操作表: users
    功能: 更新当前用户的基本信息
    参数: user_id(用户ID), name(姓名), phone(手机), email(邮箱), position(职位), department(部门), avatar(头像)
    返回: 更新后的用户信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 检查用户是否存在
        cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="用户不存在")

        # 构建更新字段
        update_fields = []
        update_values = []

        if name is not None:
            update_fields.append("name = %s")
            update_values.append(name)
        if phone is not None:
            update_fields.append("phone = %s")
            update_values.append(phone)
        if email is not None:
            update_fields.append("email = %s")
            update_values.append(email)
        if position is not None:
            update_fields.append("position = %s")
            update_values.append(position)
        if department is not None:
            update_fields.append("department = %s")
            update_values.append(department)
        if avatar is not None:
            update_fields.append("avatar = %s")
            update_values.append(avatar)

        if update_fields:
            update_values.append(user_id)
            cursor.execute(f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s", update_values)
            conn.commit()

        # 获取更新后的用户信息
        cursor.execute("""
            SELECT 
                id, account, name, role, avatar, phone, email, 
                position, department, status, created_at, updated_at
            FROM users 
            WHERE id = %s
        """, (user_id,))

        user = cursor.fetchone()

        user_data = {
            "id": str(user['id']),
            "account": user['account'],
            "name": user['name'],
            "role": user['role'],
            "avatar": user['avatar'],
            "phone": user['phone'],
            "email": user['email'],
            "position": user['position'],
            "department": user['department'],
            "status": user['status'],
            "createdAt": user['created_at'].strftime('%Y-%m-%d') if user['created_at'] else None,
            "updatedAt": user['updated_at'].strftime('%Y-%m-%d') if user['updated_at'] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新用户信息失败: {str(e)}")


# 2.6 创建反馈
@app.post("/profile/feedbacks", response_model=WrappedApiResponse)
async def create_feedback(
        request: FeedbackRequest,
        user_id: int = Depends(get_user_id)
):
    """
    创建反馈接口
    文档来源: 千人千店个人.md - 2.6 创建反馈
    操作表: user_feedbacks
    功能: 创建用户反馈记录
    参数: user_id(用户ID), content(反馈内容), type(反馈类型)
    返回: 创建的反馈信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("""
            INSERT INTO user_feedbacks (user_id, content, type, status) 
            VALUES (%s, %s, %s, '待处理')
        """, (user_id, request.content, request.type))

        feedback_id = cursor.lastrowid
        conn.commit()

        # 获取创建的反馈信息
        cursor.execute("""
            SELECT id, user_id, content, type, status, created_at, updated_at
            FROM user_feedbacks 
            WHERE id = %s
        """, (feedback_id,))

        feedback = cursor.fetchone()

        feedback_data = {
            "id": str(feedback[0]),
            "userId": str(feedback[1]),
            "content": feedback[2],
            "type": feedback[3],
            "status": feedback[4],
            "createdAt": feedback[5].strftime('%Y-%m-%d') if feedback[5] else None,
            "updatedAt": feedback[6].strftime('%Y-%m-%d') if feedback[6] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=feedback_data,
            message="提交成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建反馈失败: {str(e)}")


# 2.8 获取我的审核列表
@app.get("/profile/audits", response_model=WrappedApiResponse)
async def get_my_audits(
        status: str = Query(..., description="审核状态"),
        page: int = Query(1, description="页码"),
        pageSize: int = Query(10, description="每页数量"),
        user_id: int = Depends(get_user_id)
):
    """
    获取我的审核列表接口
    文档来源: 千人千店个人.md - 2.8 获取我的审核列表
    操作表: audit_records
    功能: 获取用户的审核列表
    参数: status(审核状态), page(页码), pageSize(每页数量), user_id(用户ID)
    返回: 审核列表数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        if status == "my":
            # 获取我的审核列表（包含审批意见和审核人信息）
            cursor.execute("""
                SELECT 
                    ar.id, ar.user_id, ar.name, ar.status, ar.type, ar.address, ar.region, 
                    ar.attribute, ar.category, ar.audit_status,
                    ar.audit_remark, ar.audit_user_id, ar.audit_time,
                    u.name AS auditor_name
                FROM audit_records ar
                LEFT JOIN users u ON ar.audit_user_id = u.id
                WHERE ar.user_id = %s
                ORDER BY ar.created_at DESC
                LIMIT %s OFFSET %s
            """, (user_id, pageSize, (page - 1) * pageSize))

            my_audits = cursor.fetchall()

            # 获取总数
            cursor.execute("SELECT COUNT(*) as total FROM audit_records WHERE user_id = %s", (user_id,))
            my_audits_total = cursor.fetchone()['total']

            audit_data = {
                "myAudits": [
                    {
                        "id": str(audit['id']),
                        "userId": str(audit['user_id']),
                        "name": audit['name'],
                        "status": audit['status'],
                        "type": audit['type'],
                        "address": audit['address'],
                        "region": audit['region'],
                        "attribute": audit['attribute'],
                        "category": audit['category'],
                        "auditStatus": audit.get('audit_status') or '0',
                        "auditRemark": audit.get('audit_remark') or None,  # 审批意见（审批不通过的原因）
                        "auditUserId": str(audit['audit_user_id']) if audit.get('audit_user_id') else None,  # 审核人ID
                        "auditorName": audit.get('auditor_name') or None,  # 审核人姓名
                        "auditTime": audit.get('audit_time').strftime("%Y-%m-%d %H:%M:%S") if audit.get('audit_time') else None  # 审核时间
                    }
                    for audit in my_audits
                ],
                "pendingAudits": [],
                "myAuditsTotal": my_audits_total,
                "page": page,
                "pageSize": pageSize,
                "total": my_audits_total
            }

        elif status == "pending":
            # 获取待我审核列表（只查询当前用户的下级提交的待审批记录）
            # 通过 users 表的 manager_id 字段判断：audit_records.user_id 对应的用户的 manager_id 应该是当前用户
            cursor.execute("""
                SELECT 
                    ar.id, ar.user_id, ar.name, ar.status, ar.type, ar.address, ar.region, 
                    ar.attribute, ar.category, ar.audit_status,
                    u.name as user_name
                FROM audit_records ar
                INNER JOIN users u ON ar.user_id = u.id
                WHERE ar.audit_status = '0' 
                AND u.manager_id = %s
                ORDER BY ar.created_at DESC
                LIMIT %s OFFSET %s
            """, (user_id, pageSize, (page - 1) * pageSize))

            pending_audits = cursor.fetchall()

            # 获取总数
            cursor.execute("""
                SELECT COUNT(*) as total 
                FROM audit_records ar
                INNER JOIN users u ON ar.user_id = u.id
                WHERE ar.audit_status = '0' 
                AND u.manager_id = %s
            """, (user_id,))
            pending_audits_total = cursor.fetchone()['total']

            audit_data = {
                "myAudits": [],
                "pendingAudits": [
                    {
                        "id": str(audit['id']),
                        "userId": str(audit['user_id']),
                        "name": audit['name'],
                        "status": audit['status'],
                        "type": audit['type'],
                        "address": audit['address'],
                        "region": audit['region'],
                        "attribute": audit['attribute'],
                        "category": audit['category']
                    }
                    for audit in pending_audits
                ],
                "pendingAuditsTotal": pending_audits_total,
                "page": page,
                "pageSize": pageSize,
                "total": pending_audits_total
            }
        else:
            raise HTTPException(status_code=400, detail="无效的审核状态")

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=audit_data,
            message="获取成功"
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取审核列表失败: {str(e)}")


# 2.10 审核操作（通过/驳回）
@app.post("/profile/audits/{audit_id}", response_model=WrappedApiResponse)
async def audit_operation(
        audit_id: int,
        request: AuditRequest,
        user_id: int = Depends(get_user_id)
):
    """
    审核操作接口
    文档来源: 千人千店个人.md - 2.10 审核操作（通过/驳回）
    操作表: audit_records
    功能: 执行审核操作（通过或驳回）
    参数: audit_id(审核ID), audit_status(审核状态), remark(审核意见), user_id(审核人ID)
    返回: 审核结果
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 先检查 audit_records 表是否有 customer_id 字段
        cursor.execute("""
            SELECT COUNT(*) as cnt
            FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
            AND TABLE_NAME = 'audit_records'
            AND COLUMN_NAME = 'customer_id'
        """)
        has_customer_id = cursor.fetchone()['cnt'] > 0
        
        # 根据是否有 customer_id 字段，构建不同的查询语句
        if has_customer_id:
            cursor.execute("SELECT id, customer_id, user_id, audit_status FROM audit_records WHERE id = %s", (audit_id,))
        else:
            cursor.execute("SELECT id, user_id, audit_status FROM audit_records WHERE id = %s", (audit_id,))
        
        audit_record = cursor.fetchone()
        if not audit_record:
            raise HTTPException(status_code=404, detail="审核记录不存在")
        
        # 验证当前用户是否有权限审批（必须是申请人的上级）
        applicant_user_id = audit_record.get('user_id') if audit_record else None
        if applicant_user_id:
            cursor.execute("SELECT manager_id FROM users WHERE id = %s", (applicant_user_id,))
            applicant_info = cursor.fetchone()
            if not applicant_info or applicant_info.get('manager_id') != user_id:
                raise HTTPException(status_code=403, detail="无权限审批此记录，只有申请人的上级才能审批")
        
        customer_id = audit_record.get('customer_id') if has_customer_id and audit_record else None
        
        # 验证审核状态值
        if request.auditStatus not in ['1', '2']:
            raise HTTPException(status_code=400, detail=f"无效的审核状态值: {request.auditStatus}，必须是 '1'（通过）或 '2'（驳回）")
        
        # 打印调试信息
        print(f"[审批操作] audit_id={audit_id}, user_id={user_id}, auditStatus={request.auditStatus}, remark={request.remark}")
        print(f"[审批操作] 当前审核记录状态: {audit_record.get('audit_status')}")

        # 更新审核状态
        update_result = cursor.execute("""
            UPDATE audit_records 
            SET audit_status = %s, audit_user_id = %s, audit_remark = %s, audit_time = NOW()
            WHERE id = %s
        """, (request.auditStatus, user_id, request.remark, audit_id))
        
        print(f"[审批操作] 更新影响行数: {update_result}")
        
        # 验证更新是否成功
        cursor.execute("SELECT audit_status FROM audit_records WHERE id = %s", (audit_id,))
        updated_record = cursor.fetchone()
        if updated_record:
            print(f"[审批操作] 更新后的审核状态: {updated_record.get('audit_status')}")
        else:
            print(f"[审批操作] 警告：更新后无法查询到审核记录")

        # 如果审批通过（audit_status = '1'），确保客户的 owner 字段是申请人的 user_id
        # 这样客户就会出现在申请人的"我的客户"列表中
        if request.auditStatus == "1" and applicant_user_id:
            # has_customer_id 已经在上面检查过了，直接使用
            if has_customer_id and customer_id:
                # 如果有 customer_id 字段，直接更新客户的 owner 字段
                cursor.execute("""
                    UPDATE customers 
                    SET owner = %s, owner_updated_at = NOW()
                    WHERE id = %s
                """, (applicant_user_id, customer_id))
                print(f"[审批操作] 已更新客户 owner 字段: customer_id={customer_id}, owner={applicant_user_id}")
            elif not has_customer_id:
                # 如果没有 customer_id 字段，通过 name 和 user_id 匹配更新
                # 获取审批记录中的客户名称
                audit_name = audit_record.get('name') if audit_record else None
                if audit_name:
                    cursor.execute("""
                        UPDATE customers c
                        INNER JOIN audit_records ar ON c.name = ar.name AND ar.user_id = %s
                        SET c.owner = %s, c.owner_updated_at = NOW()
                        WHERE ar.id = %s AND c.name = %s
                    """, (applicant_user_id, applicant_user_id, audit_id, audit_name))
                    affected_rows = cursor.rowcount
                    print(f"[审批操作] 已更新客户 owner 字段（通过名称匹配）: affected_rows={affected_rows}, name={audit_name}")
                else:
                    print(f"[审批操作] 警告：无法获取审批记录中的客户名称，跳过更新客户 owner 字段")
        
        conn.commit()
        
        # 再次验证提交后的状态
        cursor.execute("SELECT audit_status, audit_user_id, audit_time FROM audit_records WHERE id = %s", (audit_id,))
        final_record = cursor.fetchone()
        if final_record:
            print(f"[审批操作] 提交后的最终状态: audit_status={final_record.get('audit_status')}, audit_user_id={final_record.get('audit_user_id')}, audit_time={final_record.get('audit_time')}")
        else:
            print(f"[审批操作] 错误：提交后无法查询到审核记录")

        message = "审核已通过" if request.auditStatus == "1" else "审核已驳回"

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "audit_id": audit_id,
                "audit_status": request.auditStatus,
                "message": message
            },
            message=message,
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        import traceback
        error_detail = f"审核操作失败: {str(e)}\n{traceback.format_exc()}"
        print(f"[审批操作] 错误: {error_detail}")
        raise HTTPException(status_code=500, detail=f"审核操作失败: {str(e)}")


# 2.11 修改当前用户密码
@app.post("/profile/change-password", response_model=WrappedApiResponse)
async def change_user_password(
        request: ChangePasswordRequest,
        user_id: int = Depends(get_user_id)
):
    """
    修改当前用户密码接口
    文档来源: 千人千店个人.md - 2.11 修改当前用户密码
    操作表: users
    功能: 修改用户密码
    参数: user_id(用户ID), old_password(旧密码), new_password(新密码)
    返回: 修改结果
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 验证旧密码
        cursor.execute("SELECT id FROM users WHERE id = %s AND password = %s", (user_id, request.oldPassword))
        if not cursor.fetchone():
            # 原密码错误也按双层 data 包装，且保持HTTP 401
            return JSONResponse(
                status_code=401,
                content={
                    "data": {
                        "data": {
                            "success": False
                        }
                    },
                    "message": "原密码错误",
                    "status": 401
                }
            )

        # 更新密码
        cursor.execute("UPDATE users SET password = %s WHERE id = %s", (request.newPassword, user_id))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="密码修改成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"修改密码失败: {str(e)}")


# 2.12 获取指定用户信息
@app.get("/profile/user-info/{target_user_id}", response_model=WrappedApiResponse)
async def get_specific_user_info(target_user_id: int, current_user_id: int = Depends(get_user_id)):
    """
    获取指定用户信息接口
    文档来源: 千人千店个人.md - 2.12 获取指定用户信息
    操作表: users
    功能: 根据用户ID获取指定用户信息
    参数: target_user_id(目标用户ID)
    返回: 用户详细信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以查看用户信息
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以查看任何用户信息，普通用户只能查看自己的信息
        if current_user['role'] != 'admin' and current_user_id != target_user_id:
            raise HTTPException(status_code=403, detail="无权限查看该用户信息")

        cursor.execute("""
            SELECT 
                id, account, name, role, avatar, phone, email, 
                position, department, status, created_at, updated_at
            FROM users 
            WHERE id = %s
        """, (target_user_id,))

        user = cursor.fetchone()

        if not user:
            raise HTTPException(status_code=404, detail="目标用户不存在")

        user_data = {
            "id": str(user['id']),
            "account": user['account'],
            "name": user['name'],
            "role": user['role'],
            "avatar": user['avatar'],
            "phone": user['phone'],
            "email": user['email'],
            "position": user['position'],
            "department": user['department'],
            "status": user['status'],
            "createdAt": user['created_at'].strftime('%Y-%m-%d') if user['created_at'] else None,
            "updatedAt": user['updated_at'].strftime('%Y-%m-%d') if user['updated_at'] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户信息失败: {str(e)}")


# 2.13 更新指定用户信息
@app.put("/profile/user-info/{target_user_id}", response_model=WrappedApiResponse)
async def update_specific_user_info(
        target_user_id: int,
        name: Optional[str] = None,
        phone: Optional[str] = None,
        email: Optional[str] = None,
        position: Optional[str] = None,
        department: Optional[str] = None,
        current_user_id: int = Depends(get_user_id)
):
    """
    更新指定用户信息接口
    文档来源: 千人千店个人.md - 2.13 更新指定用户信息
    操作表: users
    功能: 更新指定用户的基本信息
    参数: target_user_id(目标用户ID), name(姓名), phone(手机), email(邮箱), position(职位), department(部门)
    返回: 更新后的用户信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以修改用户信息
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以修改任何用户信息，普通用户只能修改自己的信息
        if current_user['role'] != 'admin' and current_user_id != target_user_id:
            raise HTTPException(status_code=403, detail="无权限修改该用户信息")

        # 检查目标用户是否存在
        cursor.execute("SELECT id FROM users WHERE id = %s", (target_user_id,))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="目标用户不存在")

        # 构建更新字段
        update_fields = []
        update_values = []

        if name is not None:
            update_fields.append("name = %s")
            update_values.append(name)
        if phone is not None:
            update_fields.append("phone = %s")
            update_values.append(phone)
        if email is not None:
            update_fields.append("email = %s")
            update_values.append(email)
        if position is not None:
            update_fields.append("position = %s")
            update_values.append(position)
        if department is not None:
            update_fields.append("department = %s")
            update_values.append(department)

        if update_fields:
            update_values.append(target_user_id)
            cursor.execute(f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s", update_values)
            conn.commit()

        # 获取更新后的用户信息
        cursor.execute("""
            SELECT 
                id, account, name, role, avatar, phone, email, 
                position, department, status, created_at, updated_at
            FROM users 
            WHERE id = %s
        """, (target_user_id,))

        user = cursor.fetchone()

        user_data = {
            "id": str(user['id']),
            "account": user['account'],
            "name": user['name'],
            "role": user['role'],
            "avatar": user['avatar'],
            "phone": user['phone'],
            "email": user['email'],
            "position": user['position'],
            "department": user['department'],
            "status": user['status'],
            "createdAt": user['created_at'].strftime('%Y-%m-%d') if user['created_at'] else None,
            "updatedAt": user['updated_at'].strftime('%Y-%m-%d') if user['updated_at'] else None
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=user_data,
            message="更新成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新用户信息失败: {str(e)}")


# 2.14 更新指定用户头像
@app.post("/profile/avatar/{target_user_id}", response_model=WrappedApiResponse)
async def update_specific_user_avatar(target_user_id: int, request: AvatarRequest,
                                      current_user_id: int = Depends(get_user_id)):
    """
    更新指定用户头像接口
    文档来源: 千人千店个人.md - 2.14 更新指定用户头像
    操作表: users
    功能: 更新指定用户头像
    参数: target_user_id(目标用户ID), avatar(头像文件名)
    返回: 更新后的头像信息
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 验证权限：只有管理员或用户本人可以修改头像
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以修改任何用户头像，普通用户只能修改自己的头像
        if current_user['role'] != 'admin' and current_user_id != target_user_id:
            raise HTTPException(status_code=403, detail="无权限修改该用户头像")

        # 检查目标用户是否存在
        cursor.execute("SELECT id FROM users WHERE id = %s", (target_user_id,))
        if not cursor.fetchone():
            raise HTTPException(status_code=404, detail="目标用户不存在")

        # 更新头像
        cursor.execute("UPDATE users SET avatar = %s WHERE id = %s", (request.avatar, target_user_id))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={"avatar": request.avatar},
            message="上传成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新头像失败: {str(e)}")


# 2.15 修改指定用户密码
@app.post("/profile/change-password/{target_user_id}", response_model=WrappedApiResponse)
async def change_specific_user_password(
        target_user_id: int,
        old_password: str = Query(..., description="旧密码"),
        new_password: str = Query(..., description="新密码"),
        current_user_id: int = Depends(get_user_id)
):
    """
    修改指定用户密码接口
    文档来源: 千人千店个人.md - 2.15 修改指定用户密码
    操作表: users
    功能: 修改指定用户密码
    参数: target_user_id(目标用户ID), old_password(旧密码), new_password(新密码)
    返回: 修改结果
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 验证权限：只有管理员或用户本人可以修改密码
        cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
        current_user = cursor.fetchone()
        if not current_user:
            raise HTTPException(status_code=404, detail="当前用户不存在")

        # 检查权限：管理员可以修改任何用户密码，普通用户只能修改自己的密码
        if current_user['role'] != 'admin' and current_user_id != target_user_id:
            raise HTTPException(status_code=403, detail="无权限修改该用户密码")

        # 验证旧密码
        cursor.execute("SELECT id FROM users WHERE id = %s AND password = %s", (target_user_id, old_password))
        if not cursor.fetchone():
            raise HTTPException(status_code=400, detail="原密码错误")

        # 更新密码
        cursor.execute("UPDATE users SET password = %s WHERE id = %s", (new_password, target_user_id))
        conn.commit()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=None,
            message="密码修改成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"修改密码失败: {str(e)}")


# 2.13 用户资料统计
@app.get("/profile/stats", response_model=WrappedApiResponse)
async def get_user_profile_stats(user_id: int = Depends(get_user_id)):
    """
    用户资料统计接口
    文档来源: 千人千店个人.md - 1.3 用户资料统计
    操作表: users, user_follows, user_posts, post_likes, user_bookmarks
    功能: 获取用户资料统计数据，包括关注数、粉丝数、文章数、获赞数、收藏数、加入时间
    参数: 无
    返回: 用户资料统计数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 获取用户基本信息
        cursor.execute("""
            SELECT id, created_at 
            FROM users 
            WHERE id = %s
        """, (user_id,))

        user = cursor.fetchone()
        if not user:
            raise HTTPException(status_code=404, detail="用户不存在")

        # 获取关注数（该用户关注了多少人）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM user_follows 
            WHERE follower_id = %s
        """, (user_id,))
        following_count = cursor.fetchone()['count']

        # 获取粉丝数（有多少人关注了该用户）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM user_follows 
            WHERE following_id = %s
        """, (user_id,))
        followers_count = cursor.fetchone()['count']

        # 获取文章数（该用户发布的文章数量）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM user_posts 
            WHERE user_id = %s AND status = 'published'
        """, (user_id,))
        posts_count = cursor.fetchone()['count']

        # 获取获赞数（该用户所有文章获得的总点赞数）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM post_likes pl
            JOIN user_posts up ON pl.post_id = up.id
            WHERE up.user_id = %s AND up.status = 'published'
        """, (user_id,))
        likes_count = cursor.fetchone()['count']

        # 获取收藏数（该用户收藏的内容数量）
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM user_bookmarks 
            WHERE user_id = %s
        """, (user_id,))
        bookmarks_count = cursor.fetchone()['count']

        # 格式化加入时间
        join_date = user['created_at'].strftime('%Y-%m-%dT%H:%M:%SZ') if user['created_at'] else None

        # 构建返回数据
        stats_data = {
            "userId": str(user_id),
            "followingCount": following_count,
            "followersCount": followers_count,
            "postsCount": posts_count,
            "likesCount": likes_count,
            "bookmarksCount": bookmarks_count,
            "joinDate": join_date
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=stats_data,
            message="获取成功"
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户资料统计失败: {str(e)}")


# ==================== 数据分析API接口 ====================

# 3.1 获取筛选后的数据分析数据
@app.get("/analytics/{province}/{city}/{year}/{month}/{user_id}", response_model=WrappedApiResponse)
async def get_analytics_data(province: str, city: str, year: str, month: str, user_id: int,
                             current_user_id: int = Depends(get_user_id)):
    """
    获取筛选后的数据分析数据接口
    文档来源: 千人千店数据api.md - 3.1 获取筛选后的数据分析数据
    操作表: visit_records, customers, area_targets
    功能: 根据省份、城市、年份、月份、用户ID从visit_records表统计数据
    参数: province(省份), city(城市), year(年份), month(月份), user_id(用户ID，-1表示查询所有用户数据)
    返回: 数据分析数据
    """
    print(f"[DEBUG] 接口调用参数: province={province}, city={city}, year={year}, month={month}, user_id={user_id}, current_user_id={current_user_id}")
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)
        print(f"[DEBUG] 数据库连接成功")

        # 权限检查：如果user_id不是-1且不等于当前用户ID，则只返回当前用户的数据
        # 这样可以防止用户查看其他用户的数据
        # if user_id != -1 and user_id != current_user_id:
        #     user_id = current_user_id

        # 当 user_id 为 -1 时，根据当前登录用户的角色权限决定数据筛选范围
        user_ids_to_filter = []
        if user_id == -1:
            # 1. 查询当前用户的 role
            cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
            user_info = cursor.fetchone()
            if not user_info or not user_info.get('role'):
                # 如果用户不存在或没有角色，默认只查询自己的数据
                user_ids_to_filter = [current_user_id]
            else:
                user_role = user_info['role']
                print(f"[DEBUG] 当前用户角色: {user_role}")
                
                # 2. 根据 role 查询 role 表的 data_scope 字段
                cursor.execute("SELECT data_scope FROM roles WHERE name = %s", (user_role,))
                role_info = cursor.fetchone()
                data_scope = role_info.get('data_scope') if role_info else '仅本人数据'
                print(f"[DEBUG] 数据范围权限: {data_scope}")
                
                # 3. 根据 data_scope 的值决定筛选逻辑
                if data_scope == '全部数据':
                    # 不筛选 user_id，只按 province/city/year/month 筛选
                    user_ids_to_filter = []
                    print(f"[DEBUG] 权限：全部数据，不筛选用户")
                elif data_scope == '本组织数据':
                    # 查询所有 manager_id = current_user_id 的下属，加上自己的数据
                    cursor.execute("SELECT id FROM users WHERE manager_id = %s OR id = %s", (current_user_id, current_user_id))
                    subordinates = cursor.fetchall()
                    user_ids_to_filter = [row['id'] for row in subordinates]
                    print(f"[DEBUG] 权限：本组织数据，查询用户ID列表: {user_ids_to_filter}")
                else:  # '仅本人数据' 或其他值
                    # 只查询当前用户的数据
                    user_ids_to_filter = [current_user_id]
                    print(f"[DEBUG] 权限：仅本人数据，查询用户ID: {current_user_id}")
        else:
            # user_id 不是 -1，直接使用指定的 user_id
            user_ids_to_filter = [user_id]
            print(f"[DEBUG] 指定用户ID: {user_id}")

        # 构建省/市二级过滤条件：使用辅助函数支持4种格式匹配
        region_condition, region_params = build_city_like_condition(province, city, "vr.region")
        
        # 构建时间过滤条件：根据年份和月份筛选
        time_condition = "YEAR(vr.created_at) = %s AND MONTH(vr.created_at) = %s"
        
        # 构建用户过滤条件
        user_condition = ""
        user_params = []
        if user_ids_to_filter:
            # 如果有用户ID列表，使用 IN 条件
            placeholders = ','.join(['%s'] * len(user_ids_to_filter))
            user_condition = f"AND CAST(vr.created_by AS UNSIGNED) IN ({placeholders})"
            user_params = user_ids_to_filter
        # 如果 user_ids_to_filter 为空，表示不筛选用户（全部数据权限）
        

        # 从visit_records表统计数据
        # 拜访次数：所有回访记录数
        # 拜访门店数：不重复的门店数
        # 建联客户数：通过visit_records关联customers表，统计status='已建联'的客户数
        # 签约客户数：通过visit_records关联customers表，统计status='已签约'的客户数
        # 教练社交次数：coach_social=1的记录数
        # 教练社交人数：coach_social=1的不重复门店数
        # 试饮场次：trial_activity=1的记录数
        # 试饮门店数：trial_activity=1的不重复门店数
        # 活动植入次数：activity_embed=1的记录数
        # 活动植入门店数：activity_embed=1的不重复门店数
        # 赠送赞助次数：sponsorship_gift=1的记录数
        # 赠送赞助门店数：sponsorship_gift=1的不重复门店数
        # 易拉宝投入：SUM(banner_placement_quantity) WHERE banner_placement=1
        # 展柜投入：SUM(display_case_placement_quantity) WHERE display_case_placement=1
        
        base_where = f"{region_condition} AND {time_condition} {user_condition}"
        base_params = region_params + [year, month] + user_params
        
        cursor.execute(f"""
            SELECT 
                COUNT(*) as visit_count,
                COUNT(DISTINCT vr.store_id) as visit_stores,
                COUNT(DISTINCT CASE WHEN c.status = '已建联' THEN c.id END) as contact_clients,
                COUNT(DISTINCT CASE WHEN c.status = '已签约' THEN c.id END) as signed_clients,
                COUNT(CASE WHEN vr.coach_social = 1 THEN 1 END) as coach_interactions,
                COUNT(DISTINCT CASE WHEN vr.coach_social = 1 THEN vr.store_id END) as coach_contacts,
                COUNT(CASE WHEN vr.trial_activity = 1 THEN 1 END) as tasting_sessions,
                COUNT(DISTINCT CASE WHEN vr.trial_activity = 1 THEN vr.store_id END) as tasting_stores,
                COUNT(CASE WHEN vr.activity_embed = 1 THEN 1 END) as activity_insertions,
                COUNT(DISTINCT CASE WHEN vr.activity_embed = 1 THEN vr.store_id END) as activity_stores,
                COUNT(CASE WHEN vr.sponsorship_gift = 1 THEN 1 END) as gift_sponsorships,
                COUNT(DISTINCT CASE WHEN vr.sponsorship_gift = 1 THEN vr.store_id END) as gift_stores,
                COALESCE(SUM(CASE WHEN vr.banner_placement = 1 THEN vr.banner_placement_quantity ELSE 0 END), 0) as yilabao_investment,
                COALESCE(SUM(CASE WHEN vr.display_case_placement = 1 THEN vr.display_case_placement_quantity ELSE 0 END), 0) as exhibition_cabinet_investment
            FROM visit_records vr
            LEFT JOIN customers c ON vr.store_id = c.id
            WHERE {base_where}
        """, base_params)

        actual_data = cursor.fetchone()

        # 获取区域目标数据（使用辅助函数支持4种格式匹配）
        area_city_condition, area_city_params = build_city_like_condition(province, city, "city")

        cursor.execute(f"""
            SELECT 
                visit_count, visit_stores, contact_clients, signed_clients,
                coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                yilabao_investment, exhibition_cabinet_investment
            FROM area_targets 
            WHERE {area_city_condition} AND year = %s AND month = %s
        """, area_city_params + [year, month])

        target_data = cursor.fetchone()

        # 构建响应数据
        visit_data_items = [
            {
                "title": "拜访次数",
                "value": actual_data['visit_count'] if actual_data else 0,
                "target": target_data['visit_count'] if target_data else 0
            },
            {
                "title": "拜访门店数",
                "value": actual_data['visit_stores'] if actual_data else 0,
                "target": target_data['visit_stores'] if target_data else 0
            },
            {
                "title": "建联客户数",
                "value": actual_data['contact_clients'] if actual_data else 0,
                "target": target_data['contact_clients'] if target_data else 0
            },
            {
                "title": "签约客户数",
                "value": actual_data['signed_clients'] if actual_data else 0,
                "target": target_data['signed_clients'] if target_data else 0
            }
        ]

        store_activity_items = [
            {
                "title": "教练社交次数",
                "value": actual_data['coach_interactions'] if actual_data else 0,
                "target": target_data['coach_interactions'] if target_data else 0
            },
            {
                "title": "教练社交人数",
                "value": actual_data['coach_contacts'] if actual_data else 0,
                "target": target_data['coach_contacts'] if target_data else 0
            },
            {
                "title": "试饮场次",
                "value": actual_data['tasting_sessions'] if actual_data else 0,
                "target": target_data['tasting_sessions'] if target_data else 0
            },
            {
                "title": "试饮门店数",
                "value": actual_data['tasting_stores'] if actual_data else 0,
                "target": target_data['tasting_stores'] if target_data else 0
            },
            {
                "title": "活动植入次数",
                "value": actual_data['activity_insertions'] if actual_data else 0
            },
            {
                "title": "活动植入门店数",
                "value": actual_data['activity_stores'] if actual_data else 0
            },
            {
                "title": "赠送赞助次数",
                "value": actual_data['gift_sponsorships'] if actual_data else 0
            },
            {
                "title": "赠送赞助门店数",
                "value": actual_data['gift_stores'] if actual_data else 0
            },
            {
                "title": "易拉宝投入",
                "value": actual_data['yilabao_investment'] if actual_data else 0
            },
            {
                "title": "展柜投入",
                "value": actual_data['exhibition_cabinet_investment'] if actual_data else 0
            }
        ]

        analytics_data = {
            "visitDataItems": visit_data_items,
            "storeActivityItems": store_activity_items
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=analytics_data,
            message="获取成功",
            status=200
        )
    except HTTPException:
        raise
    except Exception as e:
        import traceback
        error_detail = f"获取数据分析数据失败: {str(e)}\n{traceback.format_exc()}"
        raise HTTPException(status_code=500, detail=f"获取数据分析数据失败: {str(e)}")


# 3.2 导出数据分析数据
@app.get("/analytics/export", response_model=WrappedApiResponse)
async def export_analytics_data(
        startDate: str = Query(..., description="开始日期"),
        endDate: str = Query(..., description="结束日期"),
        province: Optional[str] = Query(None, description="省份"),
        city: Optional[str] = Query(None, description="城市"),
        category: Optional[str] = Query(None, description="用户id"),
        type: Optional[str] = Query(None,
                                    description="类型：拜访次数、拜访门店数、建联客户数、签约客户数、教练社交次数、教练社交人数、试饮场次、试饮门店数、活动植入次数、活动植入门店数、赠送赞助次数、赠送赞助门店数、易拉宝投入、展柜投入"),
        page: int = Query(1, description="页码"),
        pageSize: int = Query(10, description="每页数量"),
        user_id: int = Depends(get_user_id)
):
    """
    导出数据分析数据接口
    文档来源: 千人千店数据api.md - 3.2 导出数据分析数据
    操作表: visit_records, customers, users
    功能: 导出指定条件的数据分析数据（从visit_records表查询）
    参数: start_date(开始日期), end_date(结束日期), city(城市), category(类别), type(类型), page(页码), page_size(每页数量)
    返回: 导出数据
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # type字段与数据库字段的映射关系
        type_field_mapping = {
            "拜访次数": "visit_count",
            "拜访门店数": "visit_stores",
            "建联客户数": "contact_clients",
            "签约客户数": "signed_clients",
            "教练社交次数": "coach_interactions",
            "教练社交人数": "coach_contacts",
            "试饮场次": "tasting_sessions",
            "试饮门店数": "tasting_stores",
            "活动植入次数": "activity_insertions",
            "活动植入门店数": "activity_stores",
            "赠送赞助次数": "gift_sponsorships",
            "赠送赞助门店数": "gift_stores",
            "易拉宝投入": "yilabao_investment",
            "展柜投入": "exhibition_cabinet_investment"
        }

        # 构建查询条件 - 处理日期格式
        # 如果传入的是 YYYY-MM 格式，转换为日期范围
        if len(startDate) == 7 and startDate[4] == '-':  # YYYY-MM 格式
            start_date = f"{startDate}-01 00:00:00"
            # 计算月末日期
            year, month = startDate.split('-')
            if month in ['01', '03', '05', '07', '08', '10', '12']:
                end_day = '31'
            elif month in ['04', '06', '09', '11']:
                end_day = '30'
            else:  # 2月
                end_day = '28'
            end_date = f"{startDate}-{end_day} 23:59:59"
        elif len(startDate) == 10 and startDate[4] == '-' and startDate[7] == '-':  # YYYY-MM-DD 格式
            # 处理 YYYY-MM-DD 格式的日期
            start_date = f"{startDate} 00:00:00"
            end_date = f"{endDate} 23:59:59"
        else:
            # 其他格式直接使用
            start_date = startDate
            end_date = endDate

        # 如果没有指定type，返回空数据
        if not type:
            return WrappedApiResponse(
                data={
                    "records": [],
                    "colsData": [],
                    "total": 0,
                    "targetCount": 0,
                    "completedCount": 0,
                    "completionRate": 0,
                    "page": page,
                    "pageSize": pageSize
                },
                message="success",
                status=200
            )

        # 定义14种type的查询配置和字段映射
        type_configs = {
            "拜访次数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "拜访对象", "colName": "visitObject"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "状态", "colName": "status"},
                    {"colCName": "拜访时间", "colName": "visitTime"},
                    {"colCName": "拜访地址", "colName": "address"}
                ],
                "select_fields": "id, name, contact_person, contact_method, status, created_at, address"
            },
            "拜访门店数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "拜访对象", "colName": "visitObject"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "状态", "colName": "status"},
                    {"colCName": "拜访时间", "colName": "visitTime"},
                    {"colCName": "拜访地址", "colName": "address"}
                ],
                "select_fields": "id, name, contact_person, contact_method, status, created_at, address"
            },
            "建联客户数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "join_customers": True,
                "customer_status": "已建联",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "客户属性", "colName": "attribute"},
                    {"colCName": "客户类型", "colName": "type"},
                    {"colCName": "建联日期", "colName": "contactDate"}
                ],
                "select_fields": "vr.store_id, c.name, c.contact_person, c.contact_method, c.attribute, c.type, vr.created_at"
            },
            "签约客户数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "join_customers": True,
                "customer_status": "已签约",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "客户属性", "colName": "attribute"},
                    {"colCName": "客户类型", "colName": "type"},
                    {"colCName": "签约日期", "colName": "signDate"},
                    {"colCName": "签约期限", "colName": "contractPeriod"}
                ],
                "select_fields": "vr.store_id, c.name, c.contact_person, c.contact_method, c.attribute, c.type, c.contract_start_month, c.contract_end_month"
            },
            "教练社交次数": {
                "table": "visit_records",
                "where_base": "coach_social = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "培训日期", "colName": "trainingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "教练社交人数": {
                "table": "visit_records",
                "where_base": "coach_social = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "培训日期", "colName": "trainingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "试饮场次": {
                "table": "visit_records",
                "where_base": "trial_activity = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "试饮活动日期", "colName": "tastingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "试饮门店数": {
                "table": "visit_records",
                "where_base": "trial_activity = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "试饮活动日期", "colName": "tastingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "活动植入次数": {
                "table": "visit_records",
                "where_base": "activity_embed = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "活动日期", "colName": "activityDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "活动植入门店数": {
                "table": "visit_records",
                "where_base": "activity_embed = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "活动日期", "colName": "activityDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "赠送赞助次数": {
                "table": "visit_records",
                "where_base": "sponsorship_gift = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "赠送日期", "colName": "giftDate"},
                    {"colCName": "赞助详情", "colName": "sponsorshipDetails"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, sponsorship_gift_details"
            },
            "赠送赞助门店数": {
                "table": "visit_records",
                "where_base": "sponsorship_gift = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "赠送日期", "colName": "giftDate"},
                    {"colCName": "赞助详情", "colName": "sponsorshipDetails"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, sponsorship_gift_details"
            },
            "易拉宝投入": {
                "table": "visit_records",
                "where_base": "banner_placement = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "投放日期", "colName": "placementDate"},
                    {"colCName": "投放数量", "colName": "placementQuantity"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, banner_placement_quantity"
            },
            "展柜投入": {
                "table": "visit_records",
                "where_base": "display_case_placement = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "投放日期", "colName": "placementDate"},
                    {"colCName": "投放数量", "colName": "placementQuantity"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, display_case_placement_quantity"
            }
        }

        # 获取对应的配置
        config = type_configs.get(type)
        if not config:
            raise HTTPException(status_code=400, detail=f"不支持的type类型: {type}")

        # 处理 category 参数：category 就是 user_id
        # 当 category 为 "-1" 时，根据当前登录用户的角色权限决定数据筛选范围
        # 当 category 不是 "-1" 时，使用指定的 user_id
        category_user_id = None
        if category and category != "-1":
            try:
                category_user_id = int(category)
                print(f"[DEBUG] 使用 category 参数作为 user_id: {category_user_id}")
            except (ValueError, TypeError):
                # 如果 category 不是数字，当作 -1 处理
                category_user_id = -1
                print(f"[DEBUG] category 参数不是有效数字，当作 -1 处理")
        else:
            category_user_id = -1
            print(f"[DEBUG] category 参数为 -1 或未提供，使用权限控制逻辑")
        
        # 根据 category_user_id 和当前登录用户的角色权限决定数据筛选范围
        user_ids_to_filter = []
        if category_user_id == -1:
            # 1. 查询当前用户的 role
            cursor.execute("SELECT role FROM users WHERE id = %s", (user_id,))
            user_info = cursor.fetchone()
            if not user_info or not user_info.get('role'):
                # 如果用户不存在或没有角色，默认只查询自己的数据
                user_ids_to_filter = [user_id]
            else:
                user_role = user_info['role']
                print(f"[DEBUG] 当前用户角色: {user_role}")
                
                # 2. 根据 role 查询 role 表的 data_scope 字段
                cursor.execute("SELECT data_scope FROM roles WHERE name = %s", (user_role,))
                role_info = cursor.fetchone()
                data_scope = role_info.get('data_scope') if role_info else '仅本人数据'
                print(f"[DEBUG] 数据范围权限: {data_scope}")
                
                # 3. 根据 data_scope 的值决定筛选逻辑
                if data_scope == '全部数据':
                    # 不筛选 user_id，只按其他条件筛选
                    user_ids_to_filter = []
                    print(f"[DEBUG] 权限：全部数据，不筛选用户")
                elif data_scope == '本组织数据':
                    # 查询所有 manager_id = user_id 的下属，加上自己的数据
                    cursor.execute("SELECT id FROM users WHERE manager_id = %s OR id = %s", (user_id, user_id))
                    subordinates = cursor.fetchall()
                    user_ids_to_filter = [row['id'] for row in subordinates]
                    print(f"[DEBUG] 权限：本组织数据，查询用户ID列表: {user_ids_to_filter}")
                else:  # '仅本人数据' 或其他值
                    # 只查询当前用户的数据
                    user_ids_to_filter = [user_id]
                    print(f"[DEBUG] 权限：仅本人数据，查询用户ID: {user_id}")
        else:
            # category_user_id 不是 -1，直接使用指定的 user_id
            user_ids_to_filter = [category_user_id]
            print(f"[DEBUG] 指定用户ID: {category_user_id}")
        
        # 构建查询参数和WHERE条件
        # 注意：config["where_base"] 中包含用户筛选条件，需要替换掉
        where_base = config["where_base"]
        # 检查是否需要关联 customers 表（建联客户数和签约客户数）
        join_customers = config.get("join_customers", False)
        
        # 替换 where_base 中的用户筛选条件
        if join_customers or config["table"] == "visit_records":
            # 移除原来的 CAST(created_by AS UNSIGNED) = %s 条件
            where_base = where_base.replace(" AND CAST(created_by AS UNSIGNED) = %s", "")
        elif config["table"] == "customers":
            # 移除原来的 owner = %s 条件
            where_base = where_base.replace(" AND owner = %s", "")
        
        query_params = [start_date, end_date]
        
        # 对于 join_customers 的情况，需要给 where_base 中的字段名加上表别名
        if join_customers:
            # 将 where_base 中的字段名加上表别名 vr.
            where_base = where_base.replace("created_at", "vr.created_at")
            where_base = where_base.replace("CAST(created_by AS UNSIGNED)", "CAST(vr.created_by AS UNSIGNED)")
        
        where_conditions = [where_base]
        
        # 构建用户筛选条件（不包含 AND，因为会在 join 时添加）
        user_params = []
        if user_ids_to_filter:
            # 如果有用户ID列表，使用 IN 条件
            placeholders = ','.join(['%s'] * len(user_ids_to_filter))
            if join_customers:
                # join_customers 的情况，使用 vr.created_by
                user_condition = f"CAST(vr.created_by AS UNSIGNED) IN ({placeholders})"
            elif config["table"] == "visit_records":
                # visit_records 表（非 join），使用 created_by
                user_condition = f"CAST(created_by AS UNSIGNED) IN ({placeholders})"
            elif config["table"] == "customers":
                # customers 表，使用 owner
                user_condition = f"owner IN ({placeholders})"
            where_conditions.append(user_condition)
            query_params.extend(user_ids_to_filter)
        # 如果 user_ids_to_filter 为空，表示不筛选用户（全部数据权限）

        # 处理 city 参数：如果 city 包含 "/"，尝试提取 province 和 city
        actual_province = province
        actual_city = city
        if city and not province and "/" in city:
            # 从 city 参数中提取省份和城市，如 "四川/乐山市" -> province="四川", city="乐山市"
            parts = city.split("/")
            if len(parts) >= 2:
                actual_province = parts[0]  # "四川"
                actual_city = parts[1]      # "乐山市"
                print(f"[DEBUG] 从 city 参数提取: province={actual_province}, city={actual_city}")

        # 添加city筛选（使用辅助函数支持4种格式匹配，增强健壮性）
        if actual_city:
            if join_customers or config["table"] == "visit_records":
                # visit_records表的region字段，如果提供了province和city，使用4种格式匹配
                # 对于 join_customers 的情况，使用 vr.region 避免歧义
                region_field = "vr.region" if join_customers else "region"
                if actual_province:
                    condition, condition_params = build_city_like_condition(actual_province, actual_city, region_field)
                else:
                    # 如果没有province，使用包含匹配（向后兼容）
                    condition, condition_params = build_region_contains_condition(actual_city, region_field)
                where_conditions.append(condition)
                query_params.extend(condition_params)
            elif config["table"] == "customers":
                # customers表的region字段，如果提供了province和city，使用4种格式匹配
                if actual_province:
                    condition, condition_params = build_city_like_condition(actual_province, actual_city, "region")
                else:
                    # 如果没有province，使用包含匹配（向后兼容）
                    condition, condition_params = build_region_contains_condition(actual_city, "region")
                where_conditions.append(condition)
                query_params.extend(condition_params)

        where_clause = " AND ".join(where_conditions)

        # 获取 customer_status（如果存在）
        customer_status = config.get("customer_status")
        
        # 构建SQL查询
        if join_customers:
            # 建联客户数和签约客户数：通过 visit_records 关联 customers 表查询
            # 只查询在指定时间范围内有拜访记录且状态为指定状态的客户
            if config["distinct"] and config["group_by"]:
                # 去重查询：使用子查询获取每个分组的最新记录
                sql = f"""
                    SELECT {config["select_fields"]}
                    FROM visit_records vr
                    INNER JOIN customers c ON vr.store_id = c.id
                    WHERE c.status = %s AND ({config["group_by"]}, vr.created_at) IN (
                        SELECT {config["group_by"]}, MAX(vr.created_at) as max_date
                        FROM visit_records vr
                        INNER JOIN customers c ON vr.store_id = c.id
                        WHERE c.status = %s AND {where_clause}
                        GROUP BY {config["group_by"]}
                    )
                    ORDER BY vr.created_at DESC
                    LIMIT %s OFFSET %s
                """
                query_params_with_status = [customer_status, customer_status] + query_params
            else:
                # 普通查询
                sql = f"""
                    SELECT {config["select_fields"]}
                    FROM visit_records vr
                    INNER JOIN customers c ON vr.store_id = c.id
                    WHERE c.status = %s AND {where_clause}
                    ORDER BY vr.created_at DESC
                    LIMIT %s OFFSET %s
                """
                query_params_with_status = [customer_status] + query_params
        elif config["distinct"] and config["group_by"]:
            # 去重查询：使用子查询获取每个分组的最新记录
            sql = f"""
                SELECT {config["select_fields"]}
                FROM {config["table"]}
                WHERE ({config["group_by"]}, created_at) IN (
                    SELECT {config["group_by"]}, MAX(created_at) as max_date
                    FROM {config["table"]}
                    WHERE {where_clause}
                    GROUP BY {config["group_by"]}
                )
                ORDER BY created_at DESC
                LIMIT %s OFFSET %s
            """
            query_params_with_status = query_params
        else:
            # 普通查询
            sql = f"""
                SELECT {config["select_fields"]}
                FROM {config["table"]}
                WHERE {where_clause}
                ORDER BY created_at DESC
                LIMIT %s OFFSET %s
            """
            query_params_with_status = query_params

        # 执行查询
        cursor.execute(sql, query_params_with_status + [pageSize, (page - 1) * pageSize])
        records = cursor.fetchall()

        # 获取总数
        if join_customers:
            if config["distinct"] and config["group_by"]:
                count_sql = f"""
                    SELECT COUNT(DISTINCT {config["group_by"]}) as total
                    FROM visit_records vr
                    INNER JOIN customers c ON vr.store_id = c.id
                    WHERE c.status = %s AND {where_clause}
                """
                count_params = [customer_status] + query_params
            else:
                count_sql = f"""
                    SELECT COUNT(*) as total
                    FROM visit_records vr
                    INNER JOIN customers c ON vr.store_id = c.id
                    WHERE c.status = %s AND {where_clause}
                """
                count_params = [customer_status] + query_params
        elif config["distinct"] and config["group_by"]:
            count_sql = f"""
                SELECT COUNT(DISTINCT {config["group_by"]}) as total
                FROM {config["table"]}
                WHERE {where_clause}
            """
            count_params = query_params
        else:
            count_sql = f"""
                SELECT COUNT(*) as total
                FROM {config["table"]}
                WHERE {where_clause}
            """
            count_params = query_params

        cursor.execute(count_sql, count_params)
        total = cursor.fetchone()['total']

        # 从area_targets表获取目标数
        target_count = 0
        try:
            target_field_mapping = {
                "拜访次数": "visit_count",
                "拜访门店数": "visit_stores",
                "建联客户数": "contact_clients",
                "签约客户数": "signed_clients",
                "教练社交次数": "coach_interactions",
                "教练社交人数": "coach_contacts",
                "试饮场次": "tasting_sessions",
                "试饮门店数": "tasting_stores",
                "活动植入次数": "activity_insertions",
                "活动植入门店数": "activity_stores",
                "赠送赞助次数": "gift_sponsorships",
                "赠送赞助门店数": "gift_stores",
                "易拉宝投入": "yilabao_investment",
                "展柜投入": "exhibition_cabinet_investment"
            }

            if type in target_field_mapping:
                target_field = target_field_mapping[type]
                start_year = startDate[:4]
                start_month = startDate[5:7] if len(startDate) >= 7 else None

                if start_month and actual_city:
                    # area_targets表的city字段，如果提供了province和city，使用4种格式匹配
                    if actual_province:
                        condition, condition_params = build_city_like_condition(actual_province, actual_city, "city")
                    else:
                        # 如果没有province，使用包含匹配（向后兼容）
                        condition, condition_params = build_region_contains_condition(actual_city, "city")
                    target_sql = f"""
                        SELECT SUM({target_field}) as target
                        FROM area_targets
                        WHERE year = %s AND month = %s AND {condition}
                    """
                    cursor.execute(target_sql, [start_year, start_month] + condition_params)
                    target_result = cursor.fetchone()
                    if target_result and target_result['target']:
                        target_count = int(target_result['target'])
        except Exception as e:
            print(f"获取目标数失败: {str(e)}")
            target_count = 0

        # 格式化records数据（使用英文键）
        formatted_records = []
        
        # 生成colsData字段映射列表（只生成一次，不依赖数据）
        # colsData应该是一个字段映射列表，包含所有字段的中英文映射关系
        formatted_cols_data = []
        # 添加序号字段
        formatted_cols_data.append({"colCName": "序号", "colName": "index"})
        
        # 根据不同的type添加对应的字段映射
        if type == "拜访次数":
            formatted_cols_data.append({"colCName": "拜访对象", "colName": "visitObject"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "状态", "colName": "status"})
            formatted_cols_data.append({"colCName": "拜访时间", "colName": "visitTime"})
            formatted_cols_data.append({"colCName": "拜访地址", "colName": "address"})
        elif type == "拜访门店数":
            formatted_cols_data.append({"colCName": "拜访对象", "colName": "visitObject"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "状态", "colName": "status"})
            formatted_cols_data.append({"colCName": "拜访时间", "colName": "visitTime"})
            formatted_cols_data.append({"colCName": "拜访地址", "colName": "address"})
        elif type == "建联客户数":
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "客户属性", "colName": "attribute"})
            formatted_cols_data.append({"colCName": "客户类型", "colName": "type"})
            formatted_cols_data.append({"colCName": "建联日期", "colName": "contactDate"})
        elif type == "签约客户数":
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "客户属性", "colName": "attribute"})
            formatted_cols_data.append({"colCName": "客户类型", "colName": "type"})
            formatted_cols_data.append({"colCName": "签约日期", "colName": "signDate"})
            formatted_cols_data.append({"colCName": "签约期限", "colName": "contractPeriod"})
        elif type in ["教练社交次数", "教练社交人数"]:
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "培训日期", "colName": "trainingDate"})
        elif type in ["试饮场次", "试饮门店数"]:
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "试饮活动日期", "colName": "tastingDate"})
        elif type in ["活动植入次数", "活动植入门店数"]:
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "活动日期", "colName": "activityDate"})
        elif type in ["赠送赞助次数", "赠送赞助门店数"]:
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "赠送日期", "colName": "giftDate"})
            formatted_cols_data.append({"colCName": "赞助详情", "colName": "sponsorshipDetails"})
        elif type == "易拉宝投入":
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "投放日期", "colName": "placementDate"})
            formatted_cols_data.append({"colCName": "投放数量", "colName": "placementQuantity"})
        elif type == "展柜投入":
            formatted_cols_data.append({"colCName": "客户姓名", "colName": "customerName"})
            formatted_cols_data.append({"colCName": "联系人", "colName": "contactPerson"})
            formatted_cols_data.append({"colCName": "联系方式", "colName": "contactMethod"})
            formatted_cols_data.append({"colCName": "投放日期", "colName": "placementDate"})
            formatted_cols_data.append({"colCName": "投放数量", "colName": "placementQuantity"})

        for idx, record in enumerate(records, start=(page - 1) * pageSize + 1):
            # records使用英文键
            formatted_record = {"index": idx}

            # 根据不同的type格式化字段
            if type == "拜访次数":
                formatted_record["visitObject"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                formatted_record["status"] = record.get("status", "")
                created_at = record.get("created_at")
                visit_time = created_at.strftime('%Y-%m-%d %H:%M:%S') if created_at else ""
                formatted_record["visitTime"] = visit_time
                formatted_record["address"] = record.get("address", "")
            elif type == "拜访门店数":
                formatted_record["visitObject"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                formatted_record["status"] = record.get("status", "")
                created_at = record.get("created_at")
                visit_time = created_at.strftime('%Y-%m-%d %H:%M:%S') if created_at else ""
                formatted_record["visitTime"] = visit_time
                formatted_record["address"] = record.get("address", "")
            elif type == "建联客户数":
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                formatted_record["attribute"] = record.get("attribute", "")
                formatted_record["type"] = record.get("type", "")
                created_at = record.get("created_at")
                contact_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["contactDate"] = contact_date
            elif type == "签约客户数":
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                formatted_record["attribute"] = record.get("attribute", "")
                formatted_record["type"] = record.get("type", "")
                start_month = record.get("contract_start_month")
                end_month = record.get("contract_end_month")
                sign_date = start_month.strftime('%Y-%m-%d') if start_month else ""
                formatted_record["signDate"] = sign_date
                if start_month and end_month:
                    contract_period = f"{start_month.strftime('%Y-%m-%d')} 至 {end_month.strftime('%Y-%m-%d')}"
                else:
                    contract_period = ""
                formatted_record["contractPeriod"] = contract_period
            elif type in ["教练社交次数", "教练社交人数"]:
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                training_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["trainingDate"] = training_date
            elif type in ["试饮场次", "试饮门店数"]:
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                tasting_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["tastingDate"] = tasting_date
            elif type in ["活动植入次数", "活动植入门店数"]:
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                activity_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["activityDate"] = activity_date
            elif type in ["赠送赞助次数", "赠送赞助门店数"]:
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                gift_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["giftDate"] = gift_date
                formatted_record["sponsorshipDetails"] = record.get("sponsorship_gift_details", "")
            elif type == "易拉宝投入":
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                placement_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["placementDate"] = placement_date
                placement_quantity = record.get("banner_placement_quantity", 0)
                formatted_record["placementQuantity"] = placement_quantity
            elif type == "展柜投入":
                formatted_record["customerName"] = record.get("name", "")
                formatted_record["contactPerson"] = record.get("contact_person", "")
                formatted_record["contactMethod"] = record.get("contact_method", "")
                created_at = record.get("created_at")
                placement_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                formatted_record["placementDate"] = placement_date
                placement_quantity = record.get("display_case_placement_quantity", 0)
                formatted_record["placementQuantity"] = placement_quantity

            formatted_records.append(formatted_record)

        # 计算完成率和完成数
        completed_count = len(formatted_records)
        completion_rate = round((completed_count / target_count * 100), 2) if target_count > 0 else 0

        export_data = {
            "records": formatted_records,  # records使用英文键
            "colsData": formatted_cols_data,  # colsData使用中文键
            "total": total,
            "targetCount": target_count,
            "completedCount": completed_count,
            "completionRate": completion_rate,
            "page": page,
            "pageSize": pageSize
        }

        cursor.close()
        conn.close()

        # 直接返回数据，不使用双重嵌套
        return WrappedApiResponse(
            data=export_data,
            message="success",
            status=200
        )
    except Exception as e:
        # 记录详细错误信息以便调试
        import traceback
        error_detail = f"导出数据分析数据失败: {str(e)}\n{traceback.format_exc()}"
        print(error_detail)
        raise HTTPException(status_code=500, detail=f"导出数据分析数据失败: {str(e)}")


# 3.2.1 导出数据分析数据为Excel
@app.get("/analytics/exportexcel")
async def export_analytics_data_excel(
        startDate: str = Query(..., description="开始日期"),
        endDate: str = Query(..., description="结束日期"),
        province: Optional[str] = Query(None, description="省份"),
        city: Optional[str] = Query(None, description="城市"),
        category: Optional[str] = Query(None, description="用户id"),
        current_user_id: int = Depends(get_user_id)
):
    """
    导出数据分析数据为Excel文件接口
    文档来源: 千人千店数据api.md - 3.2.1 导出数据分析数据为Excel
    操作表: visit_records, customers, users
    功能: 导出指定条件的数据分析数据为Excel文件（不分页，导出全部数据，包含14个type的详情数据）
    参数: startDate(开始日期), endDate(结束日期), province(省份), city(城市), category(用户id，-1表示根据权限查询)
    返回: Excel文件流（包含14个工作表，每个工作表对应一个type的详情数据）
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建查询条件 - 处理日期格式（与 /analytics/export 相同）
        # 如果传入的是 YYYY-MM 格式，转换为日期范围
        if len(startDate) == 7 and startDate[4] == '-':  # YYYY-MM 格式
            start_date = f"{startDate}-01 00:00:00"
            # 计算月末日期
            year, month = startDate.split('-')
            if month in ['01', '03', '05', '07', '08', '10', '12']:
                end_day = '31'
            elif month in ['04', '06', '09', '11']:
                end_day = '30'
            else:  # 2月
                end_day = '28'
            end_date = f"{startDate}-{end_day} 23:59:59"
        elif len(startDate) == 10 and startDate[4] == '-' and startDate[7] == '-':  # YYYY-MM-DD 格式
            # 处理 YYYY-MM-DD 格式的日期
            start_date = f"{startDate} 00:00:00"
            end_date = f"{endDate} 23:59:59"
        else:
            # 其他格式直接使用
            start_date = startDate
            end_date = endDate

        # 处理 category 参数：category 就是 user_id（与 /analytics/export 相同）
        # 当 category 为 "-1" 时，根据当前登录用户的角色权限决定数据筛选范围
        # 当 category 不是 "-1" 时，使用指定的 user_id
        category_user_id = None
        if category and category != "-1":
            try:
                category_user_id = int(category)
                print(f"[DEBUG] 使用 category 参数作为 user_id: {category_user_id}")
            except (ValueError, TypeError):
                # 如果 category 不是数字，当作 -1 处理
                category_user_id = -1
                print(f"[DEBUG] category 参数不是有效数字，当作 -1 处理")
        else:
            category_user_id = -1
            print(f"[DEBUG] category 参数为 -1 或未提供，使用权限控制逻辑")
        
        # 根据 category_user_id 和当前登录用户的角色权限决定数据筛选范围（与 /analytics/export 相同）
        user_ids_to_filter = []
        if category_user_id == -1:
            # 1. 查询当前用户的 role
            cursor.execute("SELECT role FROM users WHERE id = %s", (current_user_id,))
            user_info = cursor.fetchone()
            if not user_info or not user_info.get('role'):
                # 如果用户不存在或没有角色，默认只查询自己的数据
                user_ids_to_filter = [current_user_id]
            else:
                user_role = user_info['role']
                print(f"[DEBUG] 当前用户角色: {user_role}")
                
                # 2. 根据 role 查询 role 表的 data_scope 字段
                cursor.execute("SELECT data_scope FROM roles WHERE name = %s", (user_role,))
                role_info = cursor.fetchone()
                data_scope = role_info.get('data_scope') if role_info else '仅本人数据'
                print(f"[DEBUG] 数据范围权限: {data_scope}")
                
                # 3. 根据 data_scope 的值决定筛选逻辑
                if data_scope == '全部数据':
                    # 不筛选 user_id，只按其他条件筛选
                    user_ids_to_filter = []
                    print(f"[DEBUG] 权限：全部数据，不筛选用户")
                elif data_scope == '本组织数据':
                    # 查询所有 manager_id = current_user_id 的下属，加上自己的数据
                    cursor.execute("SELECT id FROM users WHERE manager_id = %s OR id = %s", (current_user_id, current_user_id))
                    subordinates = cursor.fetchall()
                    user_ids_to_filter = [row['id'] for row in subordinates]
                    print(f"[DEBUG] 权限：本组织数据，查询用户ID列表: {user_ids_to_filter}")
                else:  # '仅本人数据' 或其他值
                    # 只查询当前用户的数据
                    user_ids_to_filter = [current_user_id]
                    print(f"[DEBUG] 权限：仅本人数据，查询用户ID: {current_user_id}")
        else:
            # category_user_id 不是 -1，直接使用指定的 user_id
            user_ids_to_filter = [category_user_id]
            print(f"[DEBUG] 指定用户ID: {category_user_id}")

        # 使用与 /analytics/export 相同的 type_configs 配置
        type_configs = {
            "拜访次数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "拜访对象", "colName": "visitObject"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "状态", "colName": "status"},
                    {"colCName": "拜访时间", "colName": "visitTime"},
                    {"colCName": "拜访地址", "colName": "address"}
                ],
                "select_fields": "id, name, contact_person, contact_method, status, created_at, address"
            },
            "拜访门店数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "拜访对象", "colName": "visitObject"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "状态", "colName": "status"},
                    {"colCName": "拜访时间", "colName": "visitTime"},
                    {"colCName": "拜访地址", "colName": "address"}
                ],
                "select_fields": "id, name, contact_person, contact_method, status, created_at, address"
            },
            "建联客户数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "join_customers": True,
                "customer_status": "已建联",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "客户属性", "colName": "attribute"},
                    {"colCName": "客户类型", "colName": "type"},
                    {"colCName": "建联日期", "colName": "contactDate"}
                ],
                "select_fields": "vr.store_id, c.name, c.contact_person, c.contact_method, c.attribute, c.type, vr.created_at"
            },
            "签约客户数": {
                "table": "visit_records",
                "where_base": "created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "join_customers": True,
                "customer_status": "已签约",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "客户属性", "colName": "attribute"},
                    {"colCName": "客户类型", "colName": "type"},
                    {"colCName": "签约日期", "colName": "signDate"},
                    {"colCName": "签约期限", "colName": "contractPeriod"}
                ],
                "select_fields": "vr.store_id, c.name, c.contact_person, c.contact_method, c.attribute, c.type, c.contract_start_month, c.contract_end_month"
            },
            "教练社交次数": {
                "table": "visit_records",
                "where_base": "coach_social = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "培训日期", "colName": "trainingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "教练社交人数": {
                "table": "visit_records",
                "where_base": "coach_social = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "培训日期", "colName": "trainingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "试饮场次": {
                "table": "visit_records",
                "where_base": "trial_activity = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "试饮活动日期", "colName": "tastingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "试饮门店数": {
                "table": "visit_records",
                "where_base": "trial_activity = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "试饮活动日期", "colName": "tastingDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "活动植入次数": {
                "table": "visit_records",
                "where_base": "activity_embed = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "活动日期", "colName": "activityDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "活动植入门店数": {
                "table": "visit_records",
                "where_base": "activity_embed = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "活动日期", "colName": "activityDate"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at"
            },
            "赠送赞助次数": {
                "table": "visit_records",
                "where_base": "sponsorship_gift = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "赠送日期", "colName": "giftDate"},
                    {"colCName": "赞助详情", "colName": "sponsorshipDetails"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, sponsorship_gift_details"
            },
            "赠送赞助门店数": {
                "table": "visit_records",
                "where_base": "sponsorship_gift = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": True,
                "group_by": "store_id",
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "赠送日期", "colName": "giftDate"},
                    {"colCName": "赞助详情", "colName": "sponsorshipDetails"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, sponsorship_gift_details"
            },
            "易拉宝投入": {
                "table": "visit_records",
                "where_base": "banner_placement = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "投放日期", "colName": "placementDate"},
                    {"colCName": "投放数量", "colName": "placementQuantity"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, banner_placement_quantity"
            },
            "展柜投入": {
                "table": "visit_records",
                "where_base": "display_case_placement = 1 AND created_at BETWEEN %s AND %s AND CAST(created_by AS UNSIGNED) = %s",
                "distinct": False,
                "group_by": None,
                "cols_data": [
                    {"colCName": "序号", "colName": "index"},
                    {"colCName": "客户姓名", "colName": "customerName"},
                    {"colCName": "联系人", "colName": "contactPerson"},
                    {"colCName": "联系方式", "colName": "contactMethod"},
                    {"colCName": "投放日期", "colName": "placementDate"},
                    {"colCName": "投放数量", "colName": "placementQuantity"}
                ],
                "select_fields": "id, name, contact_person, contact_method, created_at, display_case_placement_quantity"
            }
        }

        # 创建Excel工作簿
        wb = Workbook()
        # 删除默认的sheet
        wb.remove(wb.active)

        # 设置表头样式
        header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
        header_font = Font(bold=True, color="FFFFFF", size=12)
        header_alignment = Alignment(horizontal="center", vertical="center")

        # 处理 city 参数：如果 city 包含 "/"，尝试提取 province 和 city（与 /analytics/export 相同）
        # 在循环外部处理，以便在文件名生成时使用
        actual_province = province
        actual_city = city
        if city and not province and "/" in city:
            # 从 city 参数中提取省份和城市，如 "四川/乐山市" -> province="四川", city="乐山市"
            parts = city.split("/")
            if len(parts) >= 2:
                actual_province = parts[0]  # "四川"
                actual_city = parts[1]      # "乐山市"
                print(f"[DEBUG] 从 city 参数提取: province={actual_province}, city={actual_city}")

        # 14个type列表
        type_list = [
            "拜访次数", "拜访门店数", "建联客户数", "签约客户数",
            "教练社交次数", "教练社交人数", "试饮场次", "试饮门店数",
            "活动植入次数", "活动植入门店数", "赠送赞助次数", "赠送赞助门店数",
            "易拉宝投入", "展柜投入"
        ]

        # 循环处理每个type
        for type_name in type_list:
            config = type_configs.get(type_name)
            if not config:
                continue  # 跳过不支持的type

            # 构建查询参数和WHERE条件（与 /analytics/export 相同）
            where_base = config["where_base"]
            join_customers = config.get("join_customers", False)
            
            # 替换 where_base 中的用户筛选条件
            if join_customers or config["table"] == "visit_records":
                where_base = where_base.replace(" AND CAST(created_by AS UNSIGNED) = %s", "")
            elif config["table"] == "customers":
                where_base = where_base.replace(" AND owner = %s", "")
            
            query_params = [start_date, end_date]
            
            # 对于 join_customers 的情况，需要给 where_base 中的字段名加上表别名
            if join_customers:
                where_base = where_base.replace("created_at", "vr.created_at")
                where_base = where_base.replace("CAST(created_by AS UNSIGNED)", "CAST(vr.created_by AS UNSIGNED)")
            
            where_conditions = [where_base]
            
            # 构建用户筛选条件
            if user_ids_to_filter:
                placeholders = ','.join(['%s'] * len(user_ids_to_filter))
                if join_customers:
                    user_condition = f"CAST(vr.created_by AS UNSIGNED) IN ({placeholders})"
                elif config["table"] == "visit_records":
                    user_condition = f"CAST(created_by AS UNSIGNED) IN ({placeholders})"
                elif config["table"] == "customers":
                    user_condition = f"owner IN ({placeholders})"
                where_conditions.append(user_condition)
                query_params.extend(user_ids_to_filter)

            # 添加city筛选（使用辅助函数支持4种格式匹配，增强健壮性）
            if actual_city:
                if join_customers or config["table"] == "visit_records":
                    # visit_records表的region字段，如果提供了province和city，使用4种格式匹配
                    # 对于 join_customers 的情况，使用 vr.region 避免歧义
                    region_field = "vr.region" if join_customers else "region"
                    if actual_province:
                        condition, condition_params = build_city_like_condition(actual_province, actual_city, region_field)
                    else:
                        # 如果没有province，使用包含匹配（向后兼容）
                        condition, condition_params = build_region_contains_condition(actual_city, region_field)
                    where_conditions.append(condition)
                    query_params.extend(condition_params)
                elif config["table"] == "customers":
                    # customers表的region字段，如果提供了province和city，使用4种格式匹配
                    if actual_province:
                        condition, condition_params = build_city_like_condition(actual_province, actual_city, "region")
                    else:
                        # 如果没有province，使用包含匹配（向后兼容）
                        condition, condition_params = build_region_contains_condition(actual_city, "region")
                    where_conditions.append(condition)
                    query_params.extend(condition_params)

            where_clause = " AND ".join(where_conditions)

            # 获取 customer_status（如果存在）
            customer_status = config.get("customer_status")
            
            # 构建SQL查询（不分页，导出全部数据）
            if join_customers:
                if config["distinct"] and config["group_by"]:
                    sql = f"""
                        SELECT {config["select_fields"]}
                        FROM visit_records vr
                        INNER JOIN customers c ON vr.store_id = c.id
                        WHERE c.status = %s AND ({config["group_by"]}, vr.created_at) IN (
                            SELECT {config["group_by"]}, MAX(vr.created_at) as max_date
                            FROM visit_records vr
                            INNER JOIN customers c ON vr.store_id = c.id
                            WHERE c.status = %s AND {where_clause}
                            GROUP BY {config["group_by"]}
                        )
                        ORDER BY vr.created_at DESC
                    """
                    query_params_with_status = [customer_status, customer_status] + query_params
                else:
                    sql = f"""
                        SELECT {config["select_fields"]}
                        FROM visit_records vr
                        INNER JOIN customers c ON vr.store_id = c.id
                        WHERE c.status = %s AND {where_clause}
                        ORDER BY vr.created_at DESC
                    """
                    query_params_with_status = [customer_status] + query_params
            elif config["distinct"] and config["group_by"]:
                sql = f"""
                    SELECT {config["select_fields"]}
                    FROM {config["table"]}
                    WHERE ({config["group_by"]}, created_at) IN (
                        SELECT {config["group_by"]}, MAX(created_at) as max_date
                        FROM {config["table"]}
                        WHERE {where_clause}
                        GROUP BY {config["group_by"]}
                    )
                    ORDER BY created_at DESC
                """
                query_params_with_status = query_params
            else:
                sql = f"""
                    SELECT {config["select_fields"]}
                    FROM {config["table"]}
                    WHERE {where_clause}
                    ORDER BY created_at DESC
                """
                query_params_with_status = query_params

            # 执行查询（不分页）
            cursor.execute(sql, query_params_with_status)
            records = cursor.fetchall()

            # 创建新的sheet（sheet名称限制为31个字符）
            sheet_name = type_name[:31] if len(type_name) <= 31 else type_name[:28] + "..."
            ws = wb.create_sheet(title=sheet_name)

            # 根据 type 获取表头（使用 cols_data 中的 colCName）
            headers = [col["colCName"] for col in config["cols_data"]]

            # 写入表头
            for col_idx, header in enumerate(headers, start=1):
                cell = ws.cell(row=1, column=col_idx, value=header)
                cell.fill = header_fill
                cell.font = header_font
                cell.alignment = header_alignment

            # 格式化records数据并写入Excel（与 /analytics/export 相同的格式化逻辑）
            for row_idx, record in enumerate(records, start=2):
                formatted_record = {"index": row_idx - 1}

                # 根据不同的type格式化字段（与 /analytics/export 相同）
                if type_name == "拜访次数":
                    formatted_record["visitObject"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    formatted_record["status"] = record.get("status", "")
                    created_at = record.get("created_at")
                    visit_time = created_at.strftime('%Y-%m-%d %H:%M:%S') if created_at else ""
                    formatted_record["visitTime"] = visit_time
                    formatted_record["address"] = record.get("address", "")
                elif type_name == "拜访门店数":
                    formatted_record["visitObject"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    formatted_record["status"] = record.get("status", "")
                    created_at = record.get("created_at")
                    visit_time = created_at.strftime('%Y-%m-%d %H:%M:%S') if created_at else ""
                    formatted_record["visitTime"] = visit_time
                    formatted_record["address"] = record.get("address", "")
                elif type_name == "建联客户数":
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    formatted_record["attribute"] = record.get("attribute", "")
                    formatted_record["type"] = record.get("type", "")
                    created_at = record.get("created_at")
                    contact_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["contactDate"] = contact_date
                elif type_name == "签约客户数":
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    formatted_record["attribute"] = record.get("attribute", "")
                    formatted_record["type"] = record.get("type", "")
                    start_month = record.get("contract_start_month")
                    end_month = record.get("contract_end_month")
                    sign_date = start_month.strftime('%Y-%m-%d') if start_month else ""
                    formatted_record["signDate"] = sign_date
                    if start_month and end_month:
                        contract_period = f"{start_month.strftime('%Y-%m-%d')} 至 {end_month.strftime('%Y-%m-%d')}"
                    else:
                        contract_period = ""
                    formatted_record["contractPeriod"] = contract_period
                elif type_name in ["教练社交次数", "教练社交人数"]:
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    training_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["trainingDate"] = training_date
                elif type_name in ["试饮场次", "试饮门店数"]:
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    tasting_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["tastingDate"] = tasting_date
                elif type_name in ["活动植入次数", "活动植入门店数"]:
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    activity_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["activityDate"] = activity_date
                elif type_name in ["赠送赞助次数", "赠送赞助门店数"]:
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    gift_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["giftDate"] = gift_date
                    formatted_record["sponsorshipDetails"] = record.get("sponsorship_gift_details", "")
                elif type_name == "易拉宝投入":
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    placement_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["placementDate"] = placement_date
                    placement_quantity = record.get("banner_placement_quantity", 0)
                    formatted_record["placementQuantity"] = placement_quantity
                elif type_name == "展柜投入":
                    formatted_record["customerName"] = record.get("name", "")
                    formatted_record["contactPerson"] = record.get("contact_person", "")
                    formatted_record["contactMethod"] = record.get("contact_method", "")
                    created_at = record.get("created_at")
                    placement_date = created_at.strftime('%Y-%m-%d') if created_at else ""
                    formatted_record["placementDate"] = placement_date
                    placement_quantity = record.get("display_case_placement_quantity", 0)
                    formatted_record["placementQuantity"] = placement_quantity

                # 写入数据行（按照表头顺序）
                for col_idx, col_info in enumerate(config["cols_data"], start=1):
                    col_name = col_info["colName"]
                    value = formatted_record.get(col_name, "")
                    ws.cell(row=row_idx, column=col_idx, value=value)

            # 调整列宽（根据列数动态调整）
            column_widths = [15] * len(headers)  # 默认宽度15
            for col_idx, width in enumerate(column_widths, start=1):
                ws.column_dimensions[ws.cell(row=1, column=col_idx).column_letter].width = width

            # 设置行高
            ws.row_dimensions[1].height = 25
            for row_idx in range(2, len(records) + 2):
                ws.row_dimensions[row_idx].height = 20

        # 保存到内存
        output = io.BytesIO()
        wb.save(output)
        output.seek(0)

        cursor.close()
        conn.close()

        # 生成文件名（使用日期范围和城市信息）
        city_display = actual_city or city or "全部"
        province_display = actual_province or province or "全部"
        date_display = startDate.replace("-", "_").replace("/", "_")
        
        # 生成文件名（包含中文，需要URL编码）
        from urllib.parse import quote
        filename_utf8 = f"analytics_export_{province_display}_{city_display}_{date_display}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
        # 使用 RFC 5987 格式支持 UTF-8 编码的文件名
        filename_encoded = quote(filename_utf8, safe='')
        content_disposition = f"attachment; filename*=UTF-8''{filename_encoded}"

        return StreamingResponse(
            io.BytesIO(output.read()),
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers={
                "Content-Disposition": content_disposition
            }
        )
    except ImportError:
        raise HTTPException(status_code=500, detail="导出Excel功能需要安装openpyxl库，请运行: pip install openpyxl")
    except Exception as e:
        # 记录详细错误信息以便调试
        import traceback
        error_detail = f"导出Excel失败: {str(e)}\n{traceback.format_exc()}"
        print(error_detail)
        raise HTTPException(status_code=500, detail=f"导出Excel失败: {str(e)}")


# 3.3 获取区域目标数据
@app.get("/area-target/{province}/{city}/{year}/{month}", response_model=WrappedApiResponse)
async def get_area_target_data(province: str, city: str, year: str, month: str, user_id: int = Depends(get_user_id)):
    """
    获取区域目标数据接口
    文档来源: 千人千店数据api.md - 3.3 获取区域目标数据
    操作表: area_targets
    功能: 根据省份、城市、年份、月份获取区域目标数据
    参数: province(省份), city(城市), year(年份), month(月份)
    返回: 区域目标数据（聚合所有以{province}/{city}开头的区域数据）
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建省/市二级过滤条件：使用辅助函数支持4种格式匹配
        city_condition, city_params = build_city_like_condition(province, city, "city")

        cursor.execute(f"""
            SELECT 
                year, month,
                SUM(visit_count) as visit_count, 
                SUM(visit_stores) as visit_stores, 
                SUM(contact_clients) as contact_clients, 
                SUM(signed_clients) as signed_clients,
                SUM(coach_interactions) as coach_interactions, 
                SUM(coach_contacts) as coach_contacts, 
                SUM(tasting_sessions) as tasting_sessions, 
                SUM(tasting_stores) as tasting_stores,
                SUM(activity_insertions) as activity_insertions, 
                SUM(activity_stores) as activity_stores, 
                SUM(gift_sponsorships) as gift_sponsorships, 
                SUM(gift_stores) as gift_stores,
                SUM(yilabao_investment) as yilabao_investment, 
                SUM(exhibition_cabinet_investment) as exhibition_cabinet_investment
            FROM area_targets 
            WHERE {city_condition} AND year = %s AND month = %s
            GROUP BY year, month
        """, city_params + [year, month])

        target_data = cursor.fetchone()

        if not target_data:
            # 如果没有数据，返回默认值
            target_data = {
                "city": f"{province}/{city}",
                "year": year,
                "month": month,
                "visit_count": 0,
                "visit_stores": 0,
                "contact_clients": 0,
                "signed_clients": 0,
                "coach_interactions": 0,
                "coach_contacts": 0,
                "tasting_sessions": 0,
                "tasting_stores": 0,
                "activity_insertions": 0,
                "activity_stores": 0,
                "gift_sponsorships": 0,
                "gift_stores": 0,
                "yilabao_investment": 0,
                "exhibition_cabinet_investment": 0
            }

        area_target_data = {
            "city": f"{province}/{city}",
            "year": target_data['year'],
            "month": target_data['month'],
            "visitCount": str(target_data['visit_count']),
            "visitStores": str(target_data['visit_stores']),
            "contactClients": str(target_data['contact_clients']),
            "signedClients": str(target_data['signed_clients']),
            "coachInteractions": str(target_data['coach_interactions']),
            "coachContacts": str(target_data['coach_contacts']),
            "tastingSessions": str(target_data['tasting_sessions']),
            "tastingStores": str(target_data['tasting_stores']),
            "activityInsertions": str(target_data['activity_insertions']),
            "activityStores": str(target_data['activity_stores']),
            "giftSponsorships": str(target_data['gift_sponsorships']),
            "giftStores": str(target_data['gift_stores']),
            "yilabaoInvestment": str(target_data['yilabao_investment']),
            "exhibitionCabinetInvestment": str(target_data['exhibition_cabinet_investment'])
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=area_target_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取区域目标数据失败: {str(e)}")


# 3.4 获取成员目标数据
@app.get("/member-target/{province}/{city}/{year}/{month}", response_model=WrappedApiResponse)
async def get_member_target_data(province: str, city: str, year: str, month: str, user_id: int = Depends(get_user_id)):
    """
    获取成员目标数据接口
    文档来源: 千人千店数据api.md - 3.4 获取成员目标数据
    操作表: member_targets
    功能: 根据省份、城市、年份、月份获取成员目标数据
    参数: province(省份), city(城市), year(年份), month(月份)
    返回: 成员目标数据（包含所有以{province}/{city}开头的区域的成员数据）
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 构建省/市二级过滤条件：使用辅助函数支持4种格式匹配
        city_condition, city_params = build_city_like_condition(province, city, "city")

        # 获取成员目标数据（返回该城市、年份、月份的所有成员数据，不限制user_id）
        cursor.execute(f"""
            SELECT 
                id, city, year, month, user_id, user_name, visit_stores, contact_clients, signed_clients,
                coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                yilabao_investment, exhibition_cabinet_investment
            FROM member_targets 
            WHERE {city_condition} AND year = %s AND month = %s
            ORDER BY user_id
        """, city_params + [year, month])

        member_targets = cursor.fetchall()

        # 获取区域目标数据作为总目标（使用相同的省/市匹配逻辑，聚合所有匹配区域的数据）
        cursor.execute(f"""
            SELECT 
                SUM(visit_count) as visit_count,
                SUM(visit_stores) as visit_stores, 
                SUM(contact_clients) as contact_clients, 
                SUM(signed_clients) as signed_clients, 
                SUM(coach_interactions) as coach_interactions, 
                SUM(coach_contacts) as coach_contacts,
                SUM(tasting_sessions) as tasting_sessions, 
                SUM(tasting_stores) as tasting_stores, 
                SUM(activity_insertions) as activity_insertions, 
                SUM(activity_stores) as activity_stores, 
                SUM(gift_sponsorships) as gift_sponsorships, 
                SUM(gift_stores) as gift_stores, 
                SUM(yilabao_investment) as yilabao_investment, 
                SUM(exhibition_cabinet_investment) as exhibition_cabinet_investment
            FROM area_targets 
            WHERE {city_condition} AND year = %s AND month = %s
        """, city_params + [year, month])

        area_target = cursor.fetchone()

        targets = [
            {
                "id": target['id'],
                "city": target['city'],
                "year": target['year'],
                "month": target['month'],
                "userId": str(target['user_id']),
                "userName": target['user_name'],
                "visitCount": str(target['visit_stores']),  # 前端使用visitCount，对应数据库的visit_stores
                "visitStores": str(target['visit_stores']),
                "contactClients": str(target['contact_clients']),
                "signedClients": str(target['signed_clients']),
                "coachSocialCount": str(target['coach_interactions']),  # 前端使用coachSocialCount
                "coachInteractions": str(target['coach_interactions']),
                "coachSocialMembers": str(target['coach_contacts']),  # 前端使用coachSocialMembers
                "coachContacts": str(target['coach_contacts']),
                "tastingSessions": str(target['tasting_sessions']),
                "tastingStores": str(target['tasting_stores']),
                "activityInsertions": str(target['activity_insertions']),
                "activityInsertionStores": str(target['activity_stores']),  # 前端使用activityInsertionStores
                "activityStores": str(target['activity_stores']),
                "sponsorships": str(target['gift_sponsorships']),  # 前端使用sponsorships
                "giftSponsorships": str(target['gift_sponsorships']),
                "giftStores": str(target['gift_stores']),
                "yilabaoInvestment": str(target['yilabao_investment']),
                "exhibitionCabinetInvestment": str(target['exhibition_cabinet_investment'])
            }
            for target in member_targets
        ]

        member_target_data = {
            "targets": targets,
            "targetVisitCount": area_target['visit_count'] if area_target else 0,
            "targetVisitStores": area_target['visit_stores'] if area_target else 0,
            "targetContactClients": area_target['contact_clients'] if area_target else 0,
            "targetSignedClients": area_target['signed_clients'] if area_target else 0,
            "targetCoachInteractions": area_target['coach_interactions'] if area_target else 0,
            "targetCoachContacts": area_target['coach_contacts'] if area_target else 0,
            "targetTastingSessions": area_target['tasting_sessions'] if area_target else 0,
            "targetTastingStores": area_target['tasting_stores'] if area_target else 0,
            "targetActivityInsertions": area_target['activity_insertions'] if area_target else 0,
            "targetActivityStores": area_target['activity_stores'] if area_target else 0,
            "targetGiftSponsorships": area_target['gift_sponsorships'] if area_target else 0,
            "targetGiftStores": area_target['gift_stores'] if area_target else 0,
            "targetYilabaoInvestment": area_target['yilabao_investment'] if area_target else 0,
            "targetExhibitionCabinetInvestment": area_target['exhibition_cabinet_investment'] if area_target else 0
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=member_target_data,
            message="获取成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取成员目标数据失败: {str(e)}")


# 3.5 保存区域目标数据
@app.post("/area-target", response_model=WrappedApiResponse)
async def save_area_target_data(request: Request, user_id: int = Depends(get_user_id)):
    """
    保存区域目标数据接口
    文档来源: 千人千店数据api.md - 3.5 保存区域目标数据
    操作表: area_targets
    功能: 保存区域目标数据
    参数: 区域目标数据
    返回: 保存结果
    字段映射说明:
        - 前端字段 -> 数据库字段
        - visitCount -> visit_count
        - visitStores -> visit_stores
        - contactClients -> contact_clients
        - signedClients -> signed_clients
        - coachSocialCount -> coach_interactions
        - coachSocialMembers -> coach_contacts
        - tastingSessions -> tasting_sessions
        - tastingStores -> tasting_stores
        - activityInsertions -> activity_insertions
        - activityInsertionStores -> activity_stores
        - sponsorships -> gift_sponsorships
        - giftStores -> gift_stores (如果前端有)
        - yilabaoInvestment -> yilabao_investment (如果前端有)
        - exhibitionCabinetInvestment -> exhibition_cabinet_investment (如果前端有)
    """
    try:
        # 获取请求体JSON数据
        request_data = await request.json()

        conn = get_db_connection()
        cursor = conn.cursor()

        # 字段映射：前端字段名 -> 数据库字段值
        # 注意：前端传入的newStores、trainingSessions、trainingParticipants在数据库中没有对应字段，会被忽略
        visit_count = request_data.get('visitCount', 0) or 0
        visit_stores = request_data.get('visitStores', 0) or 0
        contact_clients = request_data.get('contactClients', 0) or 0
        signed_clients = request_data.get('signedClients', 0) or 0
        # 前端使用coachSocialCount，数据库是coach_interactions
        coach_interactions = request_data.get('coachSocialCount') or request_data.get('coachInteractions', 0) or 0
        # 前端使用coachSocialMembers，数据库是coach_contacts
        coach_contacts = request_data.get('coachSocialMembers') or request_data.get('coachContacts', 0) or 0
        tasting_sessions = request_data.get('tastingSessions', 0) or 0
        tasting_stores = request_data.get('tastingStores', 0) or 0
        activity_insertions = request_data.get('activityInsertions', 0) or 0
        # 前端使用activityInsertionStores，数据库是activity_stores
        activity_stores = request_data.get('activityInsertionStores') or request_data.get('activityStores', 0) or 0
        # 前端使用sponsorships，数据库是gift_sponsorships
        gift_sponsorships = request_data.get('sponsorships') or request_data.get('giftSponsorships', 0) or 0
        gift_stores = request_data.get('giftStores', 0) or 0
        yilabao_investment = request_data.get('yilabaoInvestment', 0) or 0
        exhibition_cabinet_investment = request_data.get('exhibitionCabinetInvestment', 0) or 0

        city = request_data.get('city')
        year = request_data.get('year')
        month = request_data.get('month')

        # 验证必需字段
        if not city or not year or not month:
            raise HTTPException(status_code=400, detail="缺少必需字段: city, year, month 不能为空")

        # 检查是否已存在记录
        cursor.execute("""
            SELECT id FROM area_targets 
            WHERE city = %s AND year = %s AND month = %s
        """, (city, year, month))

        existing_record = cursor.fetchone()

        if existing_record:
            # 更新现有记录
            cursor.execute("""
                UPDATE area_targets SET 
                    visit_count = %s, visit_stores = %s, contact_clients = %s, signed_clients = %s,
                    coach_interactions = %s, coach_contacts = %s, tasting_sessions = %s, tasting_stores = %s,
                    activity_insertions = %s, activity_stores = %s, gift_sponsorships = %s, gift_stores = %s,
                    yilabao_investment = %s, exhibition_cabinet_investment = %s
                WHERE city = %s AND year = %s AND month = %s
            """, (
                visit_count, visit_stores, contact_clients, signed_clients,
                coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                yilabao_investment, exhibition_cabinet_investment,
                city, year, month
            ))
        else:
            # 插入新记录
            cursor.execute("""
                INSERT INTO area_targets (
                    city, year, month, visit_count, visit_stores, contact_clients, signed_clients,
                    coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                    activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                    yilabao_investment, exhibition_cabinet_investment
                ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """, (
                city, year, month, visit_count, visit_stores, contact_clients, signed_clients,
                coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                yilabao_investment, exhibition_cabinet_investment
            ))

        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={},
            message="保存成功",
            status=200
        )
    except KeyError as e:
        # 字段缺失错误
        missing_field = str(e).replace("'", "")
        raise HTTPException(status_code=400, detail=f"缺少必需字段: {missing_field}")
    except Exception as e:
        # 记录详细错误信息以便调试
        import traceback
        error_detail = f"保存区域目标数据失败: {str(e)}\n{traceback.format_exc()}"
        print(error_detail)
        raise HTTPException(status_code=500, detail=f"保存区域目标数据失败: {str(e)}")


# 3.6 保存成员目标数据
@app.post("/member-target", response_model=WrappedApiResponse)
async def save_member_target_data(request: Request, user_id: int = Depends(get_user_id)):
    """
    保存成员目标数据接口
    文档来源: 千人千店数据api.md - 3.6 保存成员目标数据
    操作表: member_targets, area_targets
    功能: 保存成员目标数据
    参数: 成员目标数据
    返回: 保存结果
    字段映射说明:
        - 前端字段 -> 数据库字段
        - province -> city
        - targetList -> targets
        - userId -> user_id (需要转换为int)
        - coachSocialCount -> coach_interactions
        - coachSocialMembers -> coach_contacts
        - activityInsertionStores -> activity_stores
        - sponsorships -> gift_sponsorships
    """
    try:
        # 获取请求体JSON数据
        request_data = await request.json()

        conn = get_db_connection()
        cursor = conn.cursor()

        # 获取基础信息：前端使用province，数据库使用city
        city = request_data.get('province') or request_data.get('city')
        year = request_data.get('year')
        month = request_data.get('month')
        target_list = request_data.get('targetList') or request_data.get('targets', [])

        # 验证必需字段
        if not city or not year or not month:
            raise HTTPException(status_code=400, detail="缺少必需字段: province/city, year, month 不能为空")

        if not target_list:
            raise HTTPException(status_code=400, detail="缺少必需字段: targetList 不能为空")

        # 保存区域目标数据（如果需要）
        if 'targetVisitStores' in request_data:
            cursor.execute("""
                INSERT INTO area_targets (
                    city, year, month, visit_stores, contact_clients, signed_clients,
                    coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                    activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                    yilabao_investment, exhibition_cabinet_investment
                ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    visit_stores = VALUES(visit_stores),
                    contact_clients = VALUES(contact_clients),
                    signed_clients = VALUES(signed_clients),
                    coach_interactions = VALUES(coach_interactions),
                    coach_contacts = VALUES(coach_contacts),
                    tasting_sessions = VALUES(tasting_sessions),
                    tasting_stores = VALUES(tasting_stores),
                    activity_insertions = VALUES(activity_insertions),
                    activity_stores = VALUES(activity_stores),
                    gift_sponsorships = VALUES(gift_sponsorships),
                    gift_stores = VALUES(gift_stores),
                    yilabao_investment = VALUES(yilabao_investment),
                    exhibition_cabinet_investment = VALUES(exhibition_cabinet_investment)
            """, (
                city, year, month,
                request_data.get('targetVisitStores', 0),
                request_data.get('targetContactClients', 0),
                request_data.get('targetSignedClients', 0),
                request_data.get('targetCoachInteractions', 0),
                request_data.get('targetCoachContacts', 0),
                request_data.get('targetTastingSessions', 0),
                request_data.get('targetTastingStores', 0),
                request_data.get('targetActivityInsertions', 0),
                request_data.get('targetActivityStores', 0),
                request_data.get('targetGiftSponsorships', 0),
                request_data.get('targetGiftStores', 0),
                request_data.get('targetYilabaoInvestment', 0),
                request_data.get('targetExhibitionCabinetInvestment', 0)
            ))

        # 保存成员目标数据
        for target in target_list:
            # 字段映射：前端字段名 -> 数据库字段值
            target_user_id = target.get('userId') or target.get('user_id')
            if target_user_id:
                # 转换为int类型
                try:
                    target_user_id = int(target_user_id)
                except (ValueError, TypeError):
                    print(f"警告: userId格式错误: {target_user_id}，跳过该记录")
                    continue

            user_name = target.get('userName') or target.get('user_name', '')

            # 字段映射
            # 注意：前端传入的visitCount对应visit_stores，但member_targets表没有visit_count字段
            visit_stores = target.get('visitStores') or target.get('visitCount', 0) or 0
            contact_clients = target.get('contactClients', 0) or 0
            signed_clients = target.get('signedClients', 0) or 0
            # 前端使用coachSocialCount，数据库是coach_interactions
            coach_interactions = target.get('coachSocialCount') or target.get('coachInteractions', 0) or 0
            # 前端使用coachSocialMembers，数据库是coach_contacts
            coach_contacts = target.get('coachSocialMembers') or target.get('coachContacts', 0) or 0
            tasting_sessions = target.get('tastingSessions', 0) or 0
            tasting_stores = target.get('tastingStores', 0) or 0
            activity_insertions = target.get('activityInsertions', 0) or 0
            # 前端使用activityInsertionStores，数据库是activity_stores
            activity_stores = target.get('activityInsertionStores') or target.get('activityStores', 0) or 0
            # 前端使用sponsorships，数据库是gift_sponsorships
            gift_sponsorships = target.get('sponsorships') or target.get('giftSponsorships', 0) or 0
            gift_stores = target.get('giftStores', 0) or 0
            yilabao_investment = target.get('yilabaoInvestment', 0) or 0
            exhibition_cabinet_investment = target.get('exhibitionCabinetInvestment', 0) or 0

            # 注意：前端传入的newStores、trainingSessions、trainingParticipants在数据库中没有对应字段，会被忽略

            cursor.execute("""
                INSERT INTO member_targets (
                    city, year, month, user_id, user_name, visit_stores, contact_clients, signed_clients,
                    coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                    activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                    yilabao_investment, exhibition_cabinet_investment
                ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    user_name = VALUES(user_name),
                    visit_stores = VALUES(visit_stores),
                    contact_clients = VALUES(contact_clients),
                    signed_clients = VALUES(signed_clients),
                    coach_interactions = VALUES(coach_interactions),
                    coach_contacts = VALUES(coach_contacts),
                    tasting_sessions = VALUES(tasting_sessions),
                    tasting_stores = VALUES(tasting_stores),
                    activity_insertions = VALUES(activity_insertions),
                    activity_stores = VALUES(activity_stores),
                    gift_sponsorships = VALUES(gift_sponsorships),
                    gift_stores = VALUES(gift_stores),
                    yilabao_investment = VALUES(yilabao_investment),
                    exhibition_cabinet_investment = VALUES(exhibition_cabinet_investment)
            """, (
                city, year, month, target_user_id, user_name,
                visit_stores, contact_clients, signed_clients,
                coach_interactions, coach_contacts, tasting_sessions, tasting_stores,
                activity_insertions, activity_stores, gift_sponsorships, gift_stores,
                yilabao_investment, exhibition_cabinet_investment
            ))

        # 提交事务
        conn.commit()

        # 验证保存结果
        cursor.execute("""
            SELECT COUNT(*) as count 
            FROM member_targets 
            WHERE city = %s AND year = %s AND month = %s
        """, (city, year, month))
        saved_count = cursor.fetchone()[0]
        print(f"保存成功：{saved_count} 条成员目标数据 (city={city}, year={year}, month={month})")

        cursor.close()
        conn.close()

        return WrappedApiResponse(
            data={"savedCount": saved_count},
            message="保存成功",
            status=200
        )
    except KeyError as e:
        # 字段缺失错误
        missing_field = str(e).replace("'", "")
        raise HTTPException(status_code=400, detail=f"缺少必需字段: {missing_field}")
    except Exception as e:
        # 记录详细错误信息以便调试
        import traceback
        error_detail = f"保存成员目标数据失败: {str(e)}\n{traceback.format_exc()}"
        print(error_detail)
        raise HTTPException(status_code=500, detail=f"保存成员目标数据失败: {str(e)}")


# ================================================2025年10越30日新增接口================================================================================================================================

# ========== NLP 库商机管理（无鉴权、无userid） ==========


def get_nlp_db_connection():
    """
    连接 nlp 数据库（只读/写商机数据）
    host: rm-2ze6q103m284te7f7eo.mysql.rds.aliyuncs.com
    user: ai / password: Cpt123456@
    database: nlp
    """
    return pymysql.connect(
        host="rm-2ze6q103m284te7f7eo.mysql.rds.aliyuncs.com",
        port=3306,
        user="ai",
        password="Cpt123456@",
        database="nlp",
        charset="utf8mb4",
        cursorclass=_DictCursorAlias
    )


@app.get("/api/opportunities", response_model=WrappedApiResponse)
async def api_get_opportunities(
        page: int = Query(1, ge=1),
        page_size: int = Query(20, ge=1, le=100),
        search: Optional[str] = Query(None),
        province: Optional[str] = Query(None),
        city: Optional[str] = Query(None),
        district: Optional[str] = Query(None),
        type: Optional[str] = Query(None, description="类型筛选，模糊匹配")
):
    """获取商机列表（数据源：nlp.ai商机_千人千店）"""
    try:
        conn = get_nlp_db_connection()
        cursor = conn.cursor()

        where = []
        params: list[Any] = []

        if search:
            like = f"%{search}%"
            where.append("(`店铺名称` LIKE %s OR `地址` LIKE %s OR `联系方式` LIKE %s OR `类型` LIKE %s)")
            params.extend([like, like, like, like])
        if province:
            where.append("`省` = %s")
            params.append(province)
        if city:
            where.append("`市` = %s")
            params.append(city)
        if district:
            where.append("`区` = %s")
            params.append(district)
        if type:
            where.append("`类型` LIKE %s")
            params.append(f"%{type}%")

        where_clause = (" WHERE " + " AND ".join(where)) if where else ""

        cursor.execute(f"SELECT COUNT(*) AS total FROM `ai商机_千人千店`{where_clause}", params)
        total = cursor.fetchone()["total"]

        cursor.execute(
            f"""
            SELECT `id`,`店铺名称`,`省`,`市`,`区`,`地址`,`联系方式`,`类型`,`营业时间`,`创建时间`
            FROM `ai商机_千人千店`
            {where_clause}
            ORDER BY `id` DESC
            LIMIT %s OFFSET %s
            """,
            params + [page_size, (page - 1) * page_size]
        )
        rows = cursor.fetchall()

        data_list = [
            {
                "id": r["id"],
                "店铺名称": r.get("店铺名称"),
                "省": r.get("省"),
                "市": r.get("市"),
                "区": r.get("区"),
                "地址": r.get("地址"),
                "联系方式": r.get("联系方式"),
                "类型": r.get("类型"),
                "营业时间": r.get("营业时间"),
                "创建时间": r.get("创建时间").strftime("%Y-%m-%d %H:%M:%S") if r.get("创建时间") else None,
            }
            for r in rows
        ]

        result = {
            "list": data_list,
            "pagination": {
                "page": page,
                "page_size": page_size,
                "total": total,
                "total_pages": (total + page_size - 1) // page_size,
            },
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=result, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取商机列表失败: {str(e)}")


@app.get("/api/opportunities/filters", response_model=WrappedApiResponse)
async def api_get_opportunities_filters():
    """
    获取商机筛选选项接口（无鉴权版本）
    文档来源: 筛选选项API文档.md - 备选方案：独立筛选选项接口
    操作表: nlp.ai商机_千人千店
    功能: 获取商机列表的所有筛选选项值
    返回: 筛选选项数据
    """
    try:
        conn = get_nlp_db_connection()
        cursor = conn.cursor(DictCursor)

        # 获取所有唯一的类型值
        cursor.execute("""
            SELECT DISTINCT `类型` 
            FROM `ai商机_千人千店` 
            WHERE `类型` IS NOT NULL AND `类型` != ''
            ORDER BY CONVERT(`类型` USING gbk) ASC
        """)
        rows = cursor.fetchall()

        # 提取类型值并去重排序
        type_options = sorted(set([row['类型'] for row in rows if row.get('类型')]), key=lambda x: x)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "type": type_options
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取商机筛选选项失败: {str(e)}"
        print(f"Error in api_get_opportunities_filters: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/api/opportunities/{opp_id}", response_model=WrappedApiResponse)
async def api_get_opportunity_detail(opp_id: int):
    """获取商机详情（数据源：nlp.ai商机_千人千店）"""
    try:
        if opp_id is None or opp_id < 1:
            raise HTTPException(status_code=400, detail="参数错误: opp_id")

        conn = get_nlp_db_connection()
        cursor = conn.cursor()
        cursor.execute(
            """
            SELECT `id`,`店铺名称`,`省`,`市`,`区`,`地址`,`联系方式`,`类型`,`营业时间`,`创建时间`
            FROM `ai商机_千人千店` WHERE `id` = %s
            """,
            (opp_id,)
        )
        r = cursor.fetchone()
        cursor.close()
        conn.close()

        if not r:
            return create_wrapped_response(data=None, message="商机不存在", status=404)

        data = {
            "id": r["id"],
            "店铺名称": r.get("店铺名称"),
            "省": r.get("省"),
            "市": r.get("市"),
            "区": r.get("区"),
            "地址": r.get("地址"),
            "联系方式": r.get("联系方式"),
            "类型": r.get("类型"),
            "营业时间": r.get("营业时间"),
            "创建时间": r.get("创建时间").strftime("%Y-%m-%d %H:%M:%S") if r.get("创建时间") else None,
        }
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取商机详情失败: {str(e)}")


@app.get("/api/regions", response_model=WrappedApiResponse)
async def api_get_regions():
    """
    获取地区三级联动数据接口
    数据来源：优先从nlp.ai商机_千人千店表获取，如果失败则返回预设数据
    返回格式：{ data: { data: { 省: { 市: [区] } } } }
    数据结构：扁平结构 { 省: { 市: [区] } }
    """
    # 预设的省市区数据（作为备选方案）
    default_regions = {
        "广东省": {
            "广州市": ["天河区", "越秀区", "海珠区", "荔湾区", "白云区", "黄埔区", "番禺区", "花都区", "南沙区",
                       "从化区", "增城区"],
            "深圳市": ["南山区", "福田区", "罗湖区", "宝安区", "龙岗区", "盐田区", "龙华区", "坪山区", "光明区"],
            "珠海市": ["香洲区", "斗门区", "金湾区"],
            "佛山市": ["禅城区", "南海区", "顺德区", "三水区", "高明区"],
            "东莞市": ["莞城区", "南城区", "东城区", "万江区", "石碣镇", "石龙镇", "茶山镇", "石排镇", "企石镇",
                       "横沥镇", "桥头镇", "谢岗镇", "东坑镇", "常平镇", "寮步镇", "樟木头镇", "大朗镇", "黄江镇",
                       "清溪镇", "塘厦镇", "凤岗镇", "大岭山镇", "长安镇", "虎门镇", "厚街镇", "沙田镇", "道滘镇",
                       "洪梅镇", "麻涌镇", "望牛墩镇", "中堂镇", "高埗镇"],
            "中山市": ["石岐区", "东区", "西区", "南区", "五桂山区", "火炬开发区", "小榄镇", "黄圃镇", "民众镇",
                       "东凤镇", "东升镇", "古镇镇", "沙溪镇", "坦洲镇", "港口镇", "三角镇", "横栏镇", "南头镇",
                       "阜沙镇", "南朗镇", "三乡镇", "板芙镇", "大涌镇", "神湾镇"]
        },
        "北京市": {
            "北京市": ["东城区", "西城区", "朝阳区", "丰台区", "石景山区", "海淀区", "门头沟区", "房山区", "通州区",
                       "顺义区", "昌平区", "大兴区", "怀柔区", "平谷区", "密云区", "延庆区"]
        },
        "上海市": {
            "上海市": ["黄浦区", "徐汇区", "长宁区", "静安区", "普陀区", "虹口区", "杨浦区", "浦东新区", "闵行区",
                       "宝山区", "嘉定区", "金山区", "松江区", "青浦区", "奉贤区", "崇明区"]
        },
        "浙江省": {
            "杭州市": ["上城区", "下城区", "江干区", "拱墅区", "西湖区", "滨江区", "萧山区", "余杭区", "富阳区",
                       "临安区", "桐庐县", "淳安县", "建德市"],
            "宁波市": ["海曙区", "江北区", "北仑区", "镇海区", "鄞州区", "奉化区", "象山县", "宁海县", "余姚市",
                       "慈溪市"],
            "温州市": ["鹿城区", "龙湾区", "瓯海区", "洞头区", "永嘉县", "平阳县", "苍南县", "文成县", "泰顺县",
                       "瑞安市", "乐清市"],
            "嘉兴市": ["南湖区", "秀洲区", "嘉善县", "海盐县", "海宁市", "平湖市", "桐乡市"],
            "湖州市": ["吴兴区", "南浔区", "德清县", "长兴县", "安吉县"]
        },
        "江苏省": {
            "南京市": ["玄武区", "秦淮区", "建邺区", "鼓楼区", "浦口区", "栖霞区", "雨花台区", "江宁区", "六合区",
                       "溧水区", "高淳区"],
            "苏州市": ["虎丘区", "吴中区", "相城区", "姑苏区", "吴江区", "常熟市", "张家港市", "昆山市", "太仓市"],
            "无锡市": ["锡山区", "惠山区", "滨湖区", "梁溪区", "新吴区", "江阴市", "宜兴市"],
            "常州市": ["天宁区", "钟楼区", "新北区", "武进区", "金坛区", "溧阳市"],
            "南通市": ["崇川区", "港闸区", "通州区", "如东县", "启东市", "如皋市", "海门市", "海安市"]
        }
    }

    try:
        # 优先从nlp数据库获取数据
        conn = get_nlp_db_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT DISTINCT `省` FROM `ai商机_千人千店` WHERE `省` IS NOT NULL AND `省` <> ''")
        provinces = [row["省"] for row in cursor.fetchall()]

        data: dict[str, dict[str, list[str]]] = {}
        if provinces:
            for p in provinces:
                cursor.execute(
                    "SELECT DISTINCT `市` FROM `ai商机_千人千店` WHERE `省`=%s AND `市` IS NOT NULL AND `市` <> ''",
                    (p,))
                cities = [row["市"] for row in cursor.fetchall()]
                if cities:
                    data[p] = {}
                    for c in cities:
                        cursor.execute(
                            "SELECT DISTINCT `区` FROM `ai商机_千人千店` WHERE `省`=%s AND `市`=%s AND `区` IS NOT NULL AND `区` <> ''",
                            (p, c),
                        )
                        districts = [row["区"] for row in cursor.fetchall()]
                        if districts:
                            data[p][c] = districts

        cursor.close()
        conn.close()

        # 如果从数据库获取的数据为空，使用预设数据
        if not data:
            data = default_regions
            print("使用预设的省市区数据")

        # 返回双重嵌套格式：{ data: { data: { 省: { 市: [区] } } } }
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        # 如果连接失败或查询失败，返回预设数据
        print(f"从nlp数据库获取地区数据失败: {str(e)}，使用预设数据")
        return create_wrapped_response(data=default_regions, message="success", status=200)


@app.get("/api/opportunities/statistics", response_model=WrappedApiResponse)
async def api_get_opportunity_statistics(
        province: Optional[str] = Query(None),
        city: Optional[str] = Query(None),
        district: Optional[str] = Query(None),
        type: Optional[str] = Query(None)
):
    """获取商机统计信息"""
    try:
        conn = get_nlp_db_connection()
        cursor = conn.cursor()

        where = []
        params: list[Any] = []
        if province:
            where.append("`省` = %s");
            params.append(province)
        if city:
            where.append("`市` = %s");
            params.append(city)
        if district:
            where.append("`区` = %s");
            params.append(district)
        if type:
            where.append("`类型` LIKE %s");
            params.append(f"%{type}%")
        where_clause = (" WHERE " + " AND ".join(where)) if where else ""

        # 总数
        cursor.execute(f"SELECT COUNT(*) AS cnt FROM `ai商机_千人千店`{where_clause}", params)
        total = cursor.fetchone()["cnt"]

        # 区域分布（按省）
        cursor.execute(
            f"SELECT `省` AS province, COUNT(*) AS cnt FROM `ai商机_千人千店`{where_clause} GROUP BY `省`",
            params,
        )
        region_distribution = {r["province"] or "未知": r["cnt"] for r in cursor.fetchall()}

        # 类型分布
        cursor.execute(
            f"SELECT `类型` AS t, COUNT(*) AS cnt FROM `ai商机_千人千店`{where_clause} GROUP BY `类型`",
            params,
        )
        type_distribution = {r["t"] or "未知": r["cnt"] for r in cursor.fetchall()}

        # 最近新增（基于 创建时间 字段）
        from datetime import datetime, timedelta
        today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        week_start = today_start - timedelta(days=today_start.weekday())
        month_start = today_start.replace(day=1)

        def count_since(dt: datetime) -> int:
            cursor.execute(
                f"SELECT COUNT(*) AS cnt FROM `ai商机_千人千店`{where_clause + (' AND ' if where_clause else ' WHERE ')} `创建时间` >= %s",
                params + [dt],
            )
            return cursor.fetchone()["cnt"]

        recent_added = {
            "today": count_since(today_start),
            "thisWeek": count_since(week_start),
            "thisMonth": count_since(month_start),
        }

        cursor.close();
        conn.close()
        data = {
            "total": total,
            "regionDistribution": region_distribution,
            "typeDistribution": type_distribution,
            "recentAdded": recent_added,
        }
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取商机统计失败: {str(e)}")


class OpportunityBatchItem(BaseModel):
    店铺名称: str
    省: str
    市: str
    区: str
    地址: str
    联系方式: str
    类型: str
    营业时间: Optional[str] = None


class OpportunityBatchRequest(BaseModel):
    opportunities: List[OpportunityBatchItem]


@app.post("/api/opportunities/batch", response_model=WrappedApiResponse)
async def api_opportunities_batch(req: OpportunityBatchRequest):
    """批量导入商机（插入到 nlp.ai商机_千人千店）"""
    try:
        conn = get_nlp_db_connection()
        cursor = conn.cursor()

        success = 0
        failed = 0
        errors: list[dict[str, Any]] = []

        for idx, item in enumerate(req.opportunities, start=1):
            try:
                cursor.execute(
                    """
                    INSERT INTO `ai商机_千人千店`
                    (`店铺名称`,`省`,`市`,`区`,`地址`,`联系方式`,`类型`,`营业时间`,`创建时间`)
                    VALUES (%s,%s,%s,%s,%s,%s,%s,%s,NOW())
                    """,
                    (
                        item.店铺名称, item.省, item.市, item.区, item.地址,
                        item.联系方式, item.类型, item.营业时间 or "",
                    ),
                )
                success += 1
            except Exception as ie:
                failed += 1
                errors.append({"row": idx, "error": str(ie)})

        conn.commit()
        cursor.close();
        conn.close()

        data = {"success": success, "failed": failed, "total": success + failed, "errors": errors}
        return create_wrapped_response(data=data, message="批量导入成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"批量导入失败: {str(e)}")


# ========== 用户反馈管理（无鉴权、无userid） ==========

class FeedbackStatusUpdate(BaseModel):
    status: str


@app.get("/api/feedbacks", response_model=WrappedApiResponse)
async def api_get_feedbacks(
        page: int = Query(1, ge=1),
        pageSize: int = Query(None, ge=1, le=100, description="每页数量（驼峰命名，兼容前端）"),
        page_size: int = Query(10, ge=1, le=100, description="每页数量（下划线命名，统一格式）"),
        status: Optional[str] = Query(None),
        organization: Optional[str] = Query(None),
        keyword: Optional[str] = Query(None),
):
    """
    获取反馈列表接口
    文档来源: 用户反馈管理API文档.md
    操作表: feedbacks
    功能: 获取反馈列表，支持分页和多条件筛选
    参数:
        - page: 页码，默认1
        - pageSize: 每页数量（驼峰命名，兼容前端），默认10
        - page_size: 每页数量（下划线命名，统一格式），默认10
        - status: 状态筛选（可选）
        - organization: 组织筛选（可选）
        - keyword: 搜索关键词（可选）
    返回: 分页的反馈列表
    注意:
        - 如果feedbacks表不存在，返回空列表而不是错误
        - 优先使用page_size参数，如果未提供则使用pageSize参数
    """
    # 统一使用page_size参数，如果没有则使用pageSize
    actual_page_size = page_size if page_size is not None else (pageSize if pageSize is not None else 10)

    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params: list[Any] = []
        if status:
            where.append("status = %s");
            params.append(status)
        if organization:
            where.append("organization = %s");
            params.append(organization)
        if keyword:
            like = f"%{keyword}%"
            where.append("(username LIKE %s OR content LIKE %s)")
            params.extend([like, like])
        table = "user_feedbacks"
        where_clause = (" WHERE " + " AND ".join(where)) if where else ""

        try:
            # 构建WHERE子句，需要处理表别名
            # user_feedbacks表需要通过user_id关联users表获取username和role
            count_where_clause = where_clause
            if where_clause:
                # 将字段名替换为带表别名的形式
                import re
                count_where_clause = re.sub(r'\bstatus\b', 'uf.status', where_clause)
                count_where_clause = re.sub(r'\borganization\b', 'u.department', where_clause)
                count_where_clause = re.sub(r'\busername\b', 'u.name', count_where_clause)
                count_where_clause = re.sub(r'\bcontent\b', 'uf.content', count_where_clause)
            
            # 查询总数（需要关联users表）
            count_sql = f"""
                SELECT COUNT(*) AS total 
                FROM {table} uf
                LEFT JOIN users u ON uf.user_id = u.id
                {count_where_clause}
            """
            cursor.execute(count_sql, params)
            total = cursor.fetchone()["total"]

            # 查询数据（关联users表获取username、role、organization）
            query_where_clause = where_clause
            if where_clause:
                import re
                query_where_clause = re.sub(r'\bstatus\b', 'uf.status', where_clause)
                query_where_clause = re.sub(r'\borganization\b', 'u.department', where_clause)
                query_where_clause = re.sub(r'\busername\b', 'u.name', query_where_clause)
                query_where_clause = re.sub(r'\bcontent\b', 'uf.content', query_where_clause)
            
            cursor.execute(
                f"""
                SELECT 
                    uf.id, 
                    u.name AS username, 
                    u.role, 
                    u.department AS organization, 
                    uf.content, 
                    uf.status, 
                    uf.created_at AS createTime
                FROM {table} uf
                LEFT JOIN users u ON uf.user_id = u.id
                {query_where_clause}
                ORDER BY uf.id DESC
                LIMIT %s OFFSET %s
                """,
                params + [actual_page_size, (page - 1) * actual_page_size],
            )
            rows = cursor.fetchall()

            data_list = [
                {
                    "id": r["id"],
                    "username": r.get("username"),
                    "role": r.get("role"),
                    "organization": r.get("organization"),
                    "content": r.get("content"),
                    "status": r.get("status"),
                    "createTime": r.get("createTime").strftime("%Y-%m-%d %H:%M:%S") if r.get("createTime") else None,
                }
                for r in rows
            ]
        except Exception as e:
            # 表不存在或查询失败时返回空数据，并记录错误日志
            print(f"查询feedbacks表失败: {str(e)}")
            total = 0
            data_list = []

        cursor.close();
        conn.close()
        return create_wrapped_response(
            data={
                "list": data_list,
                "pagination": {
                    "page": page,
                    "page_size": actual_page_size,
                    "total": total,
                    "total_pages": (total + actual_page_size - 1) // actual_page_size,
                },
            },
            message="获取成功",
            status=200,
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取反馈列表失败: {str(e)}")


@app.get("/api/feedbacks/{fb_id}", response_model=WrappedApiResponse)
async def api_get_feedback_detail(fb_id: int):
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)
        try:
            # 关联users表获取用户信息
            cursor.execute(
                """
                SELECT 
                    uf.id, 
                    u.name AS username, 
                    u.role, 
                    u.department AS organization, 
                    uf.content, 
                    uf.status, 
                    uf.created_at AS createTime
                FROM user_feedbacks uf
                LEFT JOIN users u ON uf.user_id = u.id
                WHERE uf.id = %s
                """,
                (fb_id,),
            )
            r = cursor.fetchone()
        except Exception:
            r = None
        cursor.close()
        conn.close()
        if not r:
            return create_wrapped_response(data=None, message="反馈不存在", status=404)
        data = {
            "id": r["id"],
            "username": r.get("username"),
            "role": r.get("role"),
            "organization": r.get("organization"),
            "content": r.get("content"),
            "status": r.get("status"),
            "createTime": r.get("createTime").strftime("%Y-%m-%d %H:%M:%S") if r.get("createTime") else None,
        }
        return create_wrapped_response(data=data, message="获取成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取反馈详情失败: {str(e)}")


@app.put("/api/feedbacks/{fb_id}/status", response_model=WrappedApiResponse)
async def api_update_feedback_status(fb_id: int, req: FeedbackStatusUpdate):
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        try:
            affected = cursor.execute("UPDATE user_feedbacks SET status=%s WHERE id=%s", (req.status, fb_id))
            conn.commit()
        except Exception:
            affected = 0
        cursor.close();
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="反馈不存在或表缺失", status=404)
        return create_wrapped_response(data={"id": fb_id, "status": req.status}, message="状态修改成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"修改反馈状态失败: {str(e)}")


@app.delete("/api/feedbacks/{fb_id}", response_model=WrappedApiResponse)
async def api_delete_feedback(fb_id: int):
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        try:
            affected = cursor.execute("DELETE FROM user_feedbacks WHERE id=%s", (fb_id,))
            conn.commit()
        except Exception:
            affected = 0
        cursor.close();
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="反馈不存在或表缺失", status=404)
        return create_wrapped_response(data=None, message="删除成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除反馈失败: {str(e)}")


@app.get("/api/feedbacks/statistics", response_model=WrappedApiResponse)
async def api_get_feedback_statistics():
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)
        total = 0
        status_count: dict[str, int] = {}
        organization_count: dict[str, int] = {}
        try:
            # 查询总数（关联users表）
            cursor.execute("SELECT COUNT(*) AS cnt FROM user_feedbacks uf LEFT JOIN users u ON uf.user_id = u.id")
            total = cursor.fetchone()["cnt"]

            # 查询状态统计（从user_feedbacks表）
            cursor.execute("SELECT uf.status, COUNT(*) AS cnt FROM user_feedbacks uf GROUP BY uf.status")
            status_count = {r["status"] or "": r["cnt"] for r in cursor.fetchall()}

            # 查询组织统计（从users表的department字段）
            cursor.execute("""
                SELECT u.department AS organization, COUNT(*) AS cnt 
                FROM user_feedbacks uf
                LEFT JOIN users u ON uf.user_id = u.id
                GROUP BY u.department
            """)
            organization_count = {r["organization"] or "": r["cnt"] for r in cursor.fetchall()}
        except Exception:
            total = 0
            status_count = {}
            organization_count = {}
        cursor.close();
        conn.close()
        return create_wrapped_response(
            data={
                "total": total,
                "statusCount": status_count,
                "organizationCount": organization_count,
            },
            message="获取成功",
            status=200,
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取反馈统计失败: {str(e)}")


# ========== 客户管理（无鉴权、无userid）- 千人千店客户管理API文档.md ==========

# ==================== 客户列表接口（无鉴权） ====================

# ==================== 商机管理接口 ====================

@app.get("/api/opportunities", response_model=WrappedApiResponse)
async def api_get_opportunities(
        province: Optional[str] = Query(None, description="省份筛选"),
        city: Optional[str] = Query(None, description="城市筛选"),
        district: Optional[str] = Query(None, description="区县筛选"),
        type: Optional[str] = Query(None, description="类型筛选"),
        keyword: Optional[str] = Query(None, description="关键词搜索（店铺名称、地址等）"),
        page: int = Query(1, ge=1, description="页码，默认 1"),
        page_size: int = Query(20, ge=1, le=100, description="每页数量，默认 20")
):
    """
    获取商机列表接口
    文档来源: 客户管理与商机管理数据隔离接口文档.md
    操作表: customers
    功能: 获取商机列表，只返回跟进人为空的记录
    参数:
        - province: 省份筛选（可选）
        - city: 城市筛选（可选）
        - district: 区县筛选（可选）
        - type: 类型筛选（可选）
        - keyword: 关键词搜索（可选，搜索店铺名称、地址等）
        - page: 页码，默认 1
        - page_size: 每页数量，默认 20
    返回: 商机列表数据（分页）
    数据隔离规则: 只返回 follow_up_person IS NULL OR follow_up_person = '' 的记录
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 构建查询条件
        where_conditions = []
        params = []

        # 商机数据隔离：只查询跟进人为空的记录
        where_conditions.append("(follow_up_person IS NULL OR follow_up_person = '')")

        # 省市区筛选 - 从region字段解析（支持4种格式匹配，增强健壮性）
        if province:
            condition, condition_params = build_region_like_condition(province, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if city:
            # 城市使用包含匹配，因为region格式可能是"四川省 成都市 锦江区"
            condition, condition_params = build_region_contains_condition(city, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if district:
            # 区县使用包含匹配
            condition, condition_params = build_region_contains_condition(district, "region")
            where_conditions.append(condition)
            params.extend(condition_params)

        # 类型筛选
        if type:
            where_conditions.append("type = %s")
            params.append(type)

        # 关键词搜索（店铺名称、地址等）
        if keyword:
            where_conditions.append("(name LIKE %s OR address LIKE %s OR contact_method LIKE %s)")
            keyword_param = f"%{keyword}%"
            params.extend([keyword_param, keyword_param, keyword_param])

        # 构建WHERE子句
        where_clause = "WHERE " + " AND ".join(where_conditions)

        # 获取总数
        count_sql = "SELECT COUNT(*) as total FROM customers " + where_clause
        cursor.execute(count_sql, params)
        total = cursor.fetchone()['total']

        # 计算分页
        offset = (page - 1) * page_size

        # 获取数据
        sql = f"""
            SELECT 
                id, name, region, address, contact_method, type,
                created_at, follow_up_person,
                `省`, `市`, `区`
            FROM customers 
            {where_clause}
            ORDER BY 
                CONVERT(`省` USING gbk) ASC,
                CONVERT(`市` USING gbk) ASC,
                CONVERT(`区` USING gbk) ASC,
                created_at DESC 
            LIMIT %s OFFSET %s
        """
        cursor.execute(sql, params + [page_size, offset])
        opportunities = cursor.fetchall()

        # 格式化数据
        formatted_opportunities = []
        for opp in opportunities:
            # 解析region字段获取省市区
            region_parts = opp.get('region', '').split('/') if opp.get('region') else []
            province_name = region_parts[0] if len(region_parts) > 0 else opp.get('省', '')
            city_name = region_parts[1] if len(region_parts) > 1 else opp.get('市', '')
            district_name = region_parts[2] if len(region_parts) > 2 else opp.get('区', '')

            formatted_opp = {
                "id": opp['id'],
                "店铺名称": opp['name'],
                "省": province_name,
                "市": city_name,
                "区": district_name,
                "地址": opp.get('address', ''),
                "联系方式": opp.get('contact_method', ''),
                "类型": opp.get('type', ''),
                "创建时间": opp['created_at'].strftime("%Y-%m-%d %H:%M:%S") if opp.get('created_at') else None,
                "follow_up_person": opp.get('follow_up_person')
            }
            formatted_opportunities.append(formatted_opp)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "data": {
                    "list": formatted_opportunities,
                    "pagination": {
                        "total": total,
                        "page": page,
                        "page_size": page_size,
                        "total_pages": (total + page_size - 1) // page_size
                    }
                }
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取商机列表失败: {str(e)}"
        print(f"Error in api_get_opportunities: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/api/opportunities/filters", response_model=WrappedApiResponse)
async def api_get_opportunities_filters():
    """
    获取商机筛选选项接口
    文档来源: 客户管理与商机管理数据隔离接口文档.md
    操作表: customers
    功能: 获取商机筛选选项，只统计跟进人为空的记录
    返回: 筛选选项数据（类型列表）
    数据隔离规则: 只统计 follow_up_person IS NULL OR follow_up_person = '' 的记录
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 获取所有唯一的类型值（只统计跟进人为空的记录）
        cursor.execute("""
            SELECT DISTINCT type 
            FROM customers 
            WHERE (follow_up_person IS NULL OR follow_up_person = '')
            AND type IS NOT NULL AND type != ''
            ORDER BY CONVERT(type USING gbk) ASC
        """)
        type_rows = cursor.fetchall()
        type_options = sorted(set([row['type'] for row in type_rows if row.get('type')]), key=lambda x: x)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "type": type_options
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取商机筛选选项失败: {str(e)}"
        print(f"Error in api_get_opportunities_filters: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/api/customers/filters", response_model=WrappedApiResponse)
async def api_get_customers_filters():
    """
    获取客户筛选选项接口（无鉴权版本）
    文档来源: 筛选选项API文档.md - 备选方案：独立筛选选项接口
    操作表: customers
    功能: 获取客户列表的所有筛选选项值（只统计跟进人有值的记录）
    返回: 筛选选项数据
    数据隔离规则: 只统计 follow_up_person IS NOT NULL AND follow_up_person != '' 的记录
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(DictCursor)

        # 客户数据隔离：只统计跟进人有值的记录
        follow_up_condition = "follow_up_person IS NOT NULL AND follow_up_person != ''"

        # 获取所有唯一的归属类型值
        cursor.execute(f"""
            SELECT DISTINCT ownership 
            FROM customers 
            WHERE {follow_up_condition}
            AND ownership IS NOT NULL
            ORDER BY ownership ASC
        """)
        ownership_rows = cursor.fetchall()

        # 归属类型映射：store -> 门店, private -> 私域
        ownership_options = []
        ownership_map = {
            'store': '门店',
            'private': '私域'
        }
        for row in ownership_rows:
            value = row.get('ownership')
            if value:
                ownership_options.append({
                    "value": value,
                    "label": ownership_map.get(value, value)
                })

        # 获取所有唯一的属性值
        cursor.execute(f"""
            SELECT DISTINCT attribute 
            FROM customers 
            WHERE {follow_up_condition}
            AND attribute IS NOT NULL AND attribute != ''
            ORDER BY CONVERT(attribute USING gbk) ASC
        """)
        attribute_rows = cursor.fetchall()
        attribute_options = sorted(set([row['attribute'] for row in attribute_rows if row.get('attribute')]),
                                   key=lambda x: x)

        # 获取所有唯一的类型值
        cursor.execute(f"""
            SELECT DISTINCT type 
            FROM customers 
            WHERE {follow_up_condition}
            AND type IS NOT NULL AND type != ''
            ORDER BY CONVERT(type USING gbk) ASC
        """)
        type_rows = cursor.fetchall()
        type_options = sorted(set([row['type'] for row in type_rows if row.get('type')]), key=lambda x: x)

        # 获取所有唯一的状态值
        cursor.execute(f"""
            SELECT DISTINCT status 
            FROM customers 
            WHERE {follow_up_condition}
            AND status IS NOT NULL AND status != ''
            ORDER BY CONVERT(status USING gbk) ASC
        """)
        status_rows = cursor.fetchall()
        status_options = sorted(set([row['status'] for row in status_rows if row.get('status')]), key=lambda x: x)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "data": {
                    "ownership": ownership_options,
                    "attribute": attribute_options,
                    "type": type_options,
                    "status": status_options
                }
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取客户筛选选项失败: {str(e)}"
        print(f"Error in api_get_customers_filters: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


@app.get("/api/customers", response_model=WrappedApiResponse)
async def api_get_customers(
        province: Optional[str] = Query(None, description="省份筛选"),
        city: Optional[str] = Query(None, description="城市筛选"),
        district: Optional[str] = Query(None, description="区县筛选"),
        source: Optional[str] = Query(None, description="来源筛选（如：爬虫获取、用户创建）"),
        property: Optional[str] = Query(None, description="属性筛选（如：商圈、社区、学校）"),
        type: Optional[str] = Query(None, description="类型筛选（如：商健、铁馆、工作室）"),
        status: Optional[str] = Query(None, description="状态筛选（如：未建联、已建联、已动销、已签约、签约到期、已闭店）"),
        keyword: Optional[str] = Query(None, description="搜索关键词（搜索客户名称、地区或电话）"),
        searchQuery: Optional[str] = Query(None, description="搜索查询（搜索客户名称、地区或电话，与keyword功能相同）"),
        owner: Optional[int] = Query(None, description="跟进人ID筛选（0=没有跟进人/客户公海，其他值=跟进人的用户ID）"),
        isMy: Optional[bool] = Query(None, description="是否查询我的客户（需要配合userId参数使用，true=查询owner=userId的记录，false=查询owner=0的记录）"),
        userId: Optional[int] = Query(None, description="用户ID（当isMy=true时必填）"),
        page: int = Query(1, ge=1, description="页码"),
        pageSize: int = Query(None, ge=1, le=100, description="每页数量（驼峰命名，兼容前端）"),
        page_size: int = Query(10, ge=1, le=100, description="每页数量（下划线命名，统一格式）")
):
    """
    获取客户列表接口（无鉴权版本）
    文档来源: 接口文档需求.md - 2.1 获取客户列表
    操作表: customers, customer_history
    功能: 获取客户列表，支持多条件筛选、搜索和分页，无鉴权要求
    业务逻辑: 默认只返回有跟进人的客户（owner != 0），不返回客户公海（owner=0）
    参数:
        - province: 省份筛选（可选）
        - city: 城市筛选（可选）
        - district: 区县筛选（可选）
        - source: 来源筛选（可选，对应数据库的created_by字段或需要根据业务逻辑判断）
        - property: 属性筛选（可选，对应数据库的attribute字段）
        - type: 类型筛选（可选）
        - status: 状态筛选（可选）
        - keyword: 搜索关键词（可选，搜索客户名称、地区或电话）
        - searchQuery: 搜索查询（可选，搜索客户名称、地区或电话，与keyword功能相同，优先级高于keyword）
        - owner: 跟进人ID筛选（可选）
            - 不提供：默认排除 owner=0 的数据，只返回有跟进人的客户（owner != 0）
            - owner=0：只查询客户公海（owner=0 或 owner IS NULL）
            - owner=1,2,3...：查询指定跟进人ID的客户
        - isMy: 是否查询我的客户（可选，true=查询owner=userId的记录，false=查询owner=0的记录，需要配合userId参数使用）
        - userId: 用户ID（可选，当isMy=true时必填）
        - page: 页码，默认1
        - pageSize: 每页数量（驼峰命名，兼容前端），默认10，最大100
        - page_size: 每页数量（下划线命名，统一格式），默认10，最大100
    返回: 分页的客户列表数据
    返回格式: {
        "code": 200,
        "message": "success",
        "data": {
            "list": [...],
            "total": 50,
            "page": 1,
            "pageSize": 10,
            "page_size": 10
        }
    }
    注意:
        - 如果数据库中没有客户数据，list将返回空数组，这是正常的
        - 优先使用page_size参数，如果未提供则使用pageSize参数
        - searchQuery和keyword功能相同，如果同时提供，优先使用searchQuery
    """
    # 统一使用page_size参数，如果没有则使用pageSize
    actual_page_size = page_size if page_size is not None else (pageSize if pageSize is not None else 10)

    # 统一搜索关键词：优先使用searchQuery，如果没有则使用keyword
    search_keyword = searchQuery if searchQuery is not None else keyword

    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 构建查询条件
        where_conditions = []
        params = []

        # 添加owner字段过滤 - 根据owner参数决定筛选逻辑
        # owner字段说明：0=没有跟进人（客户公海），其他值=跟进人的用户ID
        # 业务逻辑：客户管理页面默认只显示有跟进人的客户（owner != 0），不显示客户公海（owner=0）
        if owner is not None:
            # 如果明确指定了 owner=0，则只查询公海客户
            if owner == 0:
                where_conditions.append("(owner = 0 OR owner IS NULL)")
            # 如果指定了其他 owner 值，则查询该跟进人的客户
            else:
                where_conditions.append("owner = %s")
                params.append(owner)
        elif isMy is not None:
            # 兼容isMy参数（如果提供了isMy但没有提供owner）
            if isMy:
                # isMy=true表示查询我的客户（owner=userId），需要提供userId参数
                if userId is None:
                    raise HTTPException(status_code=400, detail="当isMy=true时，必须提供userId参数")
                where_conditions.append("owner = %s")
                params.append(userId)
            else:
                # isMy=false表示查询客户公海（owner=0）
                where_conditions.append("(owner = 0 OR owner IS NULL)")
        else:
            # 如果没有指定 owner 参数，默认排除 owner=0 的数据（只显示有跟进人的客户）
            # 这是客户管理页面的默认行为：只显示有跟进人的客户，不显示客户公海
            where_conditions.append("(owner IS NOT NULL AND owner != 0)")

        # 注意：移除了follow_up_person过滤条件，与/customers接口保持一致
        # 如果需要数据隔离（只查询跟进人有值的记录），请使用/api/customers/list接口

        # 省市区筛选 - 从region字段解析（支持4种格式匹配，增强健壮性）
        if province:
            condition, condition_params = build_region_like_condition(province, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if city:
            # 城市使用包含匹配，因为region格式可能是"四川省 成都市 锦江区"
            condition, condition_params = build_region_contains_condition(city, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if district:
            # 区县使用包含匹配
            condition, condition_params = build_region_contains_condition(district, "region")
            where_conditions.append(condition)
            params.extend(condition_params)

        # 属性筛选（property对应attribute字段）
        if property:
            where_conditions.append("attribute = %s")
            params.append(property)

        # 类型筛选
        if type:
            where_conditions.append("type = %s")
            params.append(type)

        # 状态筛选
        if status:
            where_conditions.append("status = %s")
            params.append(status)

        # 来源筛选（source对应created_by字段，需要根据业务逻辑判断）
        if source:
            # 如果source是"爬虫获取"，可能需要根据created_by字段判断
            # 这里假设created_by为空或特定值表示爬虫获取
            if source == "爬虫获取":
                where_conditions.append("(created_by IS NULL OR created_by = '' OR created_by = '系统')")
            elif source == "用户创建":
                where_conditions.append("(created_by IS NOT NULL AND created_by != '' AND created_by != '系统')")

        # 搜索关键词筛选（使用统一的search_keyword）
        if search_keyword:
            where_conditions.append(
                "(name LIKE %s OR contact_person LIKE %s OR contact_method LIKE %s OR address LIKE %s OR region LIKE %s)")
            keyword_param = f"%{search_keyword}%"
            params.extend([keyword_param, keyword_param, keyword_param, keyword_param, keyword_param])

        # 构建WHERE子句
        where_clause = ""
        if where_conditions:
            where_clause = "WHERE " + " AND ".join(where_conditions)

        # 辅助函数：将WHERE子句中的字段名加上表别名c.
        def add_table_alias_to_where_clause(clause):
            """将WHERE子句中的字段名加上表别名c."""
            if not clause:
                return clause
            import re
            # 替换所有字段名，但避免替换已经在表别名中的字段
            result = re.sub(r'\bowner\b', 'c.owner', clause)
            result = re.sub(r'\bregion\b', 'c.region', result)
            result = re.sub(r'\bname\b', 'c.name', result)
            result = re.sub(r'\bcontact_person\b', 'c.contact_person', result)
            result = re.sub(r'\bcontact_method\b', 'c.contact_method', result)
            result = re.sub(r'\baddress\b', 'c.address', result)
            result = re.sub(r'\battribute\b', 'c.attribute', result)
            result = re.sub(r'\btype\b', 'c.type', result)
            result = re.sub(r'\bstatus\b', 'c.status', result)
            result = re.sub(r'\bcreated_by\b', 'c.created_by', result)
            return result

        # 获取总数（需要将where_clause中的字段名加上表别名c.）
        count_where_clause = add_table_alias_to_where_clause(where_clause)
        count_sql = "SELECT COUNT(*) as total FROM customers c " + count_where_clause
        cursor.execute(count_sql, params)
        total = cursor.fetchone()['total']

        # 计算分页
        offset = (page - 1) * actual_page_size

        # 获取数据（LEFT JOIN users表获取owner对应的用户名称）
        where_clause_with_alias = add_table_alias_to_where_clause(where_clause)
        
        sql = """
            SELECT 
                c.id, c.name, c.is_new, c.ownership, c.region, c.address, c.contact_person, c.contact_method,
                c.status, c.contract_start_month, c.contract_end_month, c.attribute, c.type, c.area,
                c.member_count, c.coach_count, c.added_coach_count,
                c.coach_social, c.trial_activity, c.activity_embed, c.sponsorship_gift,
                c.banner_placement, c.display_case_placement,
                c.coach_social_count, c.trial_activity_count, c.activity_embed_count,
                c.sponsorship_gift_details, c.banner_placement_quantity, c.display_case_placement_quantity,
                c.remarks, c.plan, c.check_in_photo, c.created_by, c.owner, c.created_at, c.updated_at, c.owner_updated_at, c.lat, c.lng,
                c.`省`, c.`市`, c.`区`,
                u.name AS owner_name
            FROM customers c
            LEFT JOIN users u ON c.owner = u.id
        """ + where_clause_with_alias + """
            ORDER BY 
                c.updated_at DESC,
                CONVERT(c.`省` USING gbk) ASC,
                CONVERT(c.`市` USING gbk) ASC,
                CONVERT(c.`区` USING gbk) ASC
            LIMIT %s OFFSET %s
        """
        cursor.execute(sql, params + [actual_page_size, offset])
        customers = cursor.fetchall()

        # 统计跟进次数和获取最新的跟进人、最后访问时间（从customer_history表）
        follow_up_counts = {}
        follow_up_persons = {}
        last_visit_times = {}
        if customers:
            customer_ids = [str(c['id']) for c in customers]
            if customer_ids:
                placeholders = ','.join(['%s'] * len(customer_ids))
                # 统计跟进次数
                sql1 = "SELECT customer_id, COUNT(*) as cnt FROM customer_history WHERE customer_id IN (" + placeholders + ") GROUP BY customer_id"
                cursor.execute(sql1, customer_ids)
                follow_up_counts = {str(row['customer_id']): row['cnt'] for row in cursor.fetchall()}

                # 获取每个客户最新的跟进人和最后访问时间
                # 使用子查询获取每个客户最新的访问记录
                sql2 = """
                    SELECT h1.customer_id, h1.follow_up_person, h1.visit_time
                    FROM customer_history h1
                    INNER JOIN (
                        SELECT customer_id, MAX(visit_time) as max_visit_time
                        FROM customer_history
                        WHERE customer_id IN (""" + placeholders + """)
                        GROUP BY customer_id
                    ) h2 ON h1.customer_id = h2.customer_id AND h1.visit_time = h2.max_visit_time
                    WHERE h1.customer_id IN (""" + placeholders + """)
                    """
                cursor.execute(sql2, customer_ids + customer_ids)
                latest_records = cursor.fetchall()
                for record in latest_records:
                    customer_id_str = str(record['customer_id'])
                    follow_up_persons[customer_id_str] = record.get('follow_up_person')
                    if record.get('visit_time'):
                        last_visit_times[customer_id_str] = record['visit_time']

        # 格式化数据
        formatted_customers = []
        for customer in customers:
            customer_id_str = str(customer['id'])

            # 处理合同期限
            contract_period = None
            if customer['contract_start_month'] or customer['contract_end_month']:
                contract_period = {
                    "startMonth": customer['contract_start_month'].strftime("%Y-%m") if customer[
                        'contract_start_month'] else None,
                    "endMonth": customer['contract_end_month'].strftime("%Y-%m") if customer[
                        'contract_end_month'] else None
                }

            # 处理门店活动
            store_actions = {
                "coachSocial": bool(customer['coach_social']),
                "trialActivity": bool(customer['trial_activity']),
                "activityEmbed": bool(customer['activity_embed']),
                "sponsorshipGift": bool(customer['sponsorship_gift']),
                "bannerPlacement": bool(customer['banner_placement']),
                "displayCasePlacement": bool(customer['display_case_placement'])
            }

            # 处理活动数值
            action_values = {
                "coachSocialCount": customer['coach_social_count'] or 0,
                "trialActivityCount": customer['trial_activity_count'] or 0,
                "activityEmbedCount": customer['activity_embed_count'] or 0,
                "sponsorshipGiftDetails": customer['sponsorship_gift_details'],
                "bannerPlacementQuantity": customer['banner_placement_quantity'] or 0,
                "displayCasePlacementQuantity": customer['display_case_placement_quantity'] or 0
            }

            formatted_customer = {
                "id": customer_id_str,  # 需求文档中是字符串类型
                "name": customer['name'],
                "isNew": bool(customer['is_new']),
                "ownership": customer['ownership'],
                "region": customer['region'],
                "address": customer['address'],
                "contactPerson": customer['contact_person'],
                "contactMethod": customer['contact_method'],
                "status": customer['status'],
                "contractPeriod": contract_period,
                "attribute": customer['attribute'],
                "type": customer['type'],
                "area": customer['area'],
                "memberCount": customer['member_count'],
                "coachCount": customer['coach_count'],
                "addedCoachCount": customer['added_coach_count'],
                "storeActions": store_actions,
                "actionValues": action_values,
                "remarks": customer['remarks'],
                "plan": customer['plan'],
                "checkInPhoto": customer['check_in_photo'],
                "createdBy": customer['created_by'],
                "owner": customer.get('owner_name') or None,  # 跟进人名称（如果owner有值且能找到用户，返回用户名称；否则返回None）
                "boundAt": customer.get('owner_updated_at').strftime("%Y-%m-%d %H:%M:%S") if customer.get('owner_updated_at') else None,  # owner字段更新时间
                "updatedAt": customer.get('updated_at').strftime("%Y-%m-%d %H:%M:%S") if customer.get('updated_at') else None,  # 更新时间
                "followUpCount": follow_up_counts.get(customer_id_str, 0),
                "followUpPerson": follow_up_persons.get(customer_id_str),
                "lastVisitTime": last_visit_times.get(customer_id_str).strftime("%Y-%m-%d") if last_visit_times.get(
                    customer_id_str) else None
            }
            formatted_customers.append(formatted_customer)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "list": formatted_customers,
                "total": total,
                "page": page,
                "pageSize": actual_page_size,  # 保持前端兼容性，同时返回pageSize
                "page_size": actual_page_size  # 统一格式，返回page_size
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取客户列表失败: {str(e)}"
        print(f"Error in api_get_customers_list: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


# ==================== 客户列表接口（专用接口，跟进人有值） ====================

@app.get("/api/customers/list", response_model=WrappedApiResponse)
async def api_get_customers_list(
        province: Optional[str] = Query(None, description="省份筛选"),
        city: Optional[str] = Query(None, description="城市筛选"),
        district: Optional[str] = Query(None, description="区县筛选"),
        source: Optional[str] = Query(None, description="归属类型筛选（store=门店, private=私域）"),
        property: Optional[str] = Query(None, description="属性筛选（商圈、社区、学校）"),
        type: Optional[str] = Query(None, description="类型筛选（商健、铁馆、私教馆）"),
        status: Optional[str] = Query(None, description="状态筛选（未建联、已建联、已签约等）"),
        keyword: Optional[str] = Query(None, description="关键词搜索（客户名称、联系方式等）"),
        page: int = Query(1, ge=1, description="页码，默认 1"),
        pageSize: int = Query(10, ge=1, le=100, description="每页数量，默认 10")
):
    """
    获取客户列表接口（专用接口，只返回跟进人有值的记录）
    文档来源: 客户管理与商机管理数据隔离接口文档.md
    操作表: customers
    功能: 获取客户列表，只返回跟进人有值的记录
    参数:
        - province: 省份筛选（可选）
        - city: 城市筛选（可选）
        - district: 区县筛选（可选）
        - source: 归属类型筛选（可选，store=门店, private=私域）
        - property: 属性筛选（可选，商圈、社区、学校）
        - type: 类型筛选（可选，商健、铁馆、私教馆）
        - status: 状态筛选（可选，未建联、已建联、已签约等）
        - keyword: 关键词搜索（可选，客户名称、联系方式等）
        - page: 页码，默认 1
        - pageSize: 每页数量，默认 10
    返回: 客户列表数据（分页）
    数据隔离规则: 只返回 follow_up_person IS NOT NULL AND follow_up_person != '' 的记录
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 构建查询条件
        where_conditions = []
        params = []

        # 客户数据隔离：只查询跟进人有值的记录
        where_conditions.append("(follow_up_person IS NOT NULL AND follow_up_person != '')")

        # 省市区筛选 - 从region字段解析（支持4种格式匹配，增强健壮性）
        if province:
            condition, condition_params = build_region_like_condition(province, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if city:
            # 城市使用包含匹配，因为region格式可能是"四川省 成都市 锦江区"
            condition, condition_params = build_region_contains_condition(city, "region")
            where_conditions.append(condition)
            params.extend(condition_params)
        if district:
            # 区县使用包含匹配
            condition, condition_params = build_region_contains_condition(district, "region")
            where_conditions.append(condition)
            params.extend(condition_params)

        # 归属类型筛选（source对应ownership字段）
        if source:
            where_conditions.append("ownership = %s")
            params.append(source)

        # 属性筛选（property对应attribute字段）
        if property:
            where_conditions.append("attribute = %s")
            params.append(property)

        # 类型筛选
        if type:
            where_conditions.append("type = %s")
            params.append(type)

        # 状态筛选
        if status:
            where_conditions.append("status = %s")
            params.append(status)

        # 关键词搜索（客户名称、联系方式等）
        if keyword:
            where_conditions.append("(name LIKE %s OR contact_person LIKE %s OR contact_method LIKE %s OR address LIKE %s)")
            keyword_param = f"%{keyword}%"
            params.extend([keyword_param, keyword_param, keyword_param, keyword_param])

        # 构建WHERE子句
        where_clause = "WHERE " + " AND ".join(where_conditions)

        # 获取总数
        count_sql = "SELECT COUNT(*) as total FROM customers " + where_clause
        cursor.execute(count_sql, params)
        total = cursor.fetchone()['total']

        # 计算分页
        offset = (page - 1) * pageSize

        # 获取数据
        sql = f"""
            SELECT 
                id, name, ownership, region, address, contact_person, contact_method,
                attribute, type, status, follow_up_person, created_at,
                `省`, `市`, `区`
            FROM customers 
            {where_clause}
            ORDER BY 
                CONVERT(`省` USING gbk) ASC,
                CONVERT(`市` USING gbk) ASC,
                CONVERT(`区` USING gbk) ASC,
                created_at DESC 
            LIMIT %s OFFSET %s
        """
        cursor.execute(sql, params + [pageSize, offset])
        customers = cursor.fetchall()

        # 格式化数据
        formatted_customers = []
        for customer in customers:
            # 解析region字段获取省市区
            region_parts = customer.get('region', '').split('/') if customer.get('region') else []
            province_name = region_parts[0] if len(region_parts) > 0 else customer.get('省', '')
            city_name = region_parts[1] if len(region_parts) > 1 else customer.get('市', '')
            district_name = region_parts[2] if len(region_parts) > 2 else customer.get('区', '')

            formatted_customer = {
                "id": customer['id'],
                "name": customer['name'],
                "contact": customer.get('contact_person', ''),
                "phone": customer.get('contact_method', ''),
                "address": customer.get('address', ''),
                "province": province_name,
                "city": city_name,
                "district": district_name,
                "ownership": customer.get('ownership', ''),
                "attribute": customer.get('attribute', ''),
                "type": customer.get('type', ''),
                "status": customer.get('status', ''),
                "follow_up_person": customer.get('follow_up_person', ''),
                "createdAt": customer['created_at'].strftime("%Y-%m-%d %H:%M:%S") if customer.get('created_at') else None
            }
            formatted_customers.append(formatted_customer)

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "data": {
                    "list": formatted_customers,
                    "pagination": {
                        "total": total,
                        "page": page,
                        "pageSize": pageSize,
                        "total_pages": (total + pageSize - 1) // pageSize
                    },
                    "filters": {
                        # 这里可以添加筛选选项，或者前端调用 /api/customers/filters 接口
                    }
                }
            },
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_msg = f"获取客户列表失败: {str(e)}"
        print(f"Error in api_get_customers_list: {error_msg}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


# ==================== 客户详情接口（含历史数据，无鉴权） ====================

# 添加客户详情接口的别名路径，支持前端使用的路径格式
@app.get("/api/customers/list/{id}/detail", response_model=WrappedApiResponse)
async def api_get_customer_detail_with_history_alias(id: int):
    """
    获取客户详情接口（别名路径，兼容前端路径格式）
    文档来源: 后端接口修改需求文档.md
    功能: 与 /api/customers/{id}/detail 功能相同，只是路径不同
    注意: 前端使用 /api/customers/list/{id}/detail 路径时会调用此接口
    """
    return await api_get_customer_detail_with_history(id)


@app.get("/api/customers/{id}/detail", response_model=WrappedApiResponse)
async def api_get_customer_detail_with_history(id: int):
    """
    获取客户详情接口（含历史数据，无鉴权版本）
    文档来源: 接口文档需求.md - 2.2 获取客户详情（含历史数据）
    操作表: customers, customer_history
    功能: 根据客户ID获取客户详细信息，包含历史访问记录，无鉴权要求
    参数:
        - id: 客户ID（路径参数）
    返回: 客户详细信息，包含历史记录
    返回格式: {
        "code": 200,
        "message": "success",
        "data": {
            "id": "GYM0001",
            "name": "力美健身俱乐部",
            "contact": "张经理",
            "phone": "13812345678",
            "address": "万达广场3楼",
            "province": "湖南省",
            "city": "长沙市",
            "district": "芙蓉区",
            "dataSource": "爬虫获取",
            "property": "商圈",
            "type": "商健",
            "status": "已建联",
            "area": 500,
            "memberCount": 200,
            "coachCount": 10,
            "addedCoachCount": 8,
            "followUpPerson": "张三",
            "followUpCount": 3,
            "lastVisitTime": "2024-01-20",
            "createTime": "2024-01-15 10:30:00",
            "checkInPhoto": "https://example.com/photo.jpg",
            "historyRecords": [...]
        }
    }
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 获取客户基本信息（只查询跟进人有值的记录）
        cursor.execute("""
            SELECT 
                id, name, is_new, ownership, region, address, contact_person, contact_method,
                status, contract_start_month, contract_end_month, attribute, type, area,
                member_count, coach_count, added_coach_count,
                coach_social, trial_activity, activity_embed, sponsorship_gift,
                banner_placement, display_case_placement,
                coach_social_count, trial_activity_count, activity_embed_count,
                sponsorship_gift_details, banner_placement_quantity, display_case_placement_quantity,
                remarks, plan, check_in_photo, created_by, owner, created_at, lat, lng
            FROM customers 
            WHERE id = %s 
            AND follow_up_person IS NOT NULL 
            AND follow_up_person != ''
        """, (id,))

        customer = cursor.fetchone()

        if not customer:
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="客户不存在或该记录为商机数据", status=404)

        # 解析region字段获取省市区
        province = None
        city = None
        district = None
        if customer['region']:
            parts = customer['region'].strip().split()
            if len(parts) >= 1:
                province = parts[0]
            if len(parts) >= 2:
                city = parts[1]
            if len(parts) >= 3:
                district = parts[2]

        # 判断数据来源
        data_source = "用户创建"
        if not customer['created_by'] or customer['created_by'] == '' or customer['created_by'] == '系统':
            data_source = "爬虫获取"

        # 获取跟进次数
        cursor.execute("SELECT COUNT(*) as cnt FROM customer_history WHERE customer_id = %s", (id,))
        follow_up_count = cursor.fetchone()['cnt']

        # 获取最新的跟进人和最后访问时间
        cursor.execute("""
            SELECT follow_up_person, visit_time
            FROM customer_history 
            WHERE customer_id = %s
            ORDER BY visit_time DESC
            LIMIT 1
        """, (id,))
        latest_history = cursor.fetchone()
        latest_follow_up_person = latest_history.get('follow_up_person') if latest_history else None
        latest_visit_time = latest_history.get('visit_time') if latest_history else None

        # 获取历史记录
        cursor.execute("""
            SELECT 
                h.visit_time, c.name, c.region, c.address, c.contact_person, c.contact_method,
                c.attribute, c.type, h.status, h.area, h.member_count, h.coach_count,
                h.added_coach_count, h.follow_up_person, h.check_in_photo
            FROM customer_history h
            LEFT JOIN customers c ON h.customer_id = c.id
            WHERE h.customer_id = %s
            ORDER BY h.visit_time DESC
        """, (id,))
        history_rows = cursor.fetchall()

        history_records = [
            {
                "visitTime": r["visit_time"].strftime("%Y-%m-%d") if r["visit_time"] else None,
                "name": r["name"],
                "region": r["region"],
                "address": r["address"],
                "contactPerson": r["contact_person"],
                "contactMethod": r["contact_method"],
                "attribute": r["attribute"],
                "type": r["type"],
                "status": r["status"],
                "area": r["area"],
                "memberCount": r["member_count"],
                "coachCount": r["coach_count"],
                "addedCoachCount": r["added_coach_count"],
                "followUpPerson": r["follow_up_person"],
                "checkInPhoto": r["check_in_photo"]
            }
            for r in history_rows
        ]

        # 构建返回数据
        customer_data = {
            "id": str(customer['id']),  # 需求文档中是字符串类型
            "name": customer['name'],
            "contact": customer['contact_person'],
            "phone": customer['contact_method'],
            "address": customer['address'],
            "province": province,
            "city": city,
            "district": district,
            "dataSource": data_source,
            "property": customer['attribute'],  # property对应attribute字段
            "type": customer['type'],
            "status": customer['status'],
            "area": customer['area'],
            "memberCount": customer['member_count'],
            "coachCount": customer['coach_count'],
            "addedCoachCount": customer['added_coach_count'],
            "followUpPerson": latest_follow_up_person,
            "followUpCount": follow_up_count,
            "lastVisitTime": latest_visit_time.strftime("%Y-%m-%d") if latest_visit_time else None,
            "createTime": customer['created_at'].strftime("%Y-%m-%d %H:%M:%S") if customer['created_at'] else None,
            "checkInPhoto": customer['check_in_photo'],
            "historyRecords": history_records
        }

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data=customer_data,
            message="success",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取客户详情失败: {str(e)}")


# ==================== 导出客户数据接口（无鉴权） ====================

@app.get("/api/customers/export")
async def api_export_customers(
        province: Optional[str] = Query(None, description="省份筛选"),
        city: Optional[str] = Query(None, description="城市筛选"),
        district: Optional[str] = Query(None, description="区县筛选"),
        source: Optional[str] = Query(None, description="来源筛选"),
        property: Optional[str] = Query(None, description="属性筛选"),
        type: Optional[str] = Query(None, description="类型筛选"),
        status: Optional[str] = Query(None, description="状态筛选"),
        keyword: Optional[str] = Query(None, description="搜索关键词"),
        format: str = Query("json", description="导出格式：json或csv")
):
    """
    导出客户数据接口（无鉴权版本）
    文档来源: 接口文档需求.md - 2.3 导出客户数据
    操作表: customers
    功能: 导出客户数据，支持多种筛选条件，可导出为JSON或CSV格式
    参数: 同客户列表接口的筛选条件，额外增加format参数（json或csv）
    返回: JSON数据或CSV文件流
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 构建查询条件（与列表接口相同）
        where_conditions = []
        params = []

        if province:
            where_conditions.append("region LIKE %s")
            params.append(f"{province}%")
        if city:
            where_conditions.append("region LIKE %s")
            params.append(f"%{city}%")
        if district:
            where_conditions.append("region LIKE %s")
            params.append(f"%{district}%")
        if property:
            where_conditions.append("attribute = %s")
            params.append(property)
        if type:
            where_conditions.append("type = %s")
            params.append(type)
        if status:
            where_conditions.append("status = %s")
            params.append(status)
        if source:
            if source == "爬虫获取":
                where_conditions.append("(created_by IS NULL OR created_by = '' OR created_by = '系统')")
            elif source == "用户创建":
                where_conditions.append("(created_by IS NOT NULL AND created_by != '' AND created_by != '系统')")
        if keyword:
            where_conditions.append(
                "(name LIKE %s OR contact_person LIKE %s OR contact_method LIKE %s OR address LIKE %s OR region LIKE %s)")
            keyword_param = f"%{keyword}%"
            params.extend([keyword_param, keyword_param, keyword_param, keyword_param, keyword_param])

        where_clause = ""
        if where_conditions:
            where_clause = "WHERE " + " AND ".join(where_conditions)

        # 获取所有数据（不分页）
        sql = """
            SELECT 
                id, name, is_new, ownership, region, address, contact_person, contact_method,
                status, contract_start_month, contract_end_month, attribute, type, area,
                member_count, coach_count, added_coach_count,
                remarks, plan, check_in_photo, created_by, created_at, lat, lng,
                `省`, `市`, `区`
            FROM customers 
        """ + where_clause + """
            ORDER BY 
                CONVERT(`省` USING gbk) ASC,
                CONVERT(`市` USING gbk) ASC,
                CONVERT(`区` USING gbk) ASC,
                created_at DESC
        """
        cursor.execute(sql, params)
        customers = cursor.fetchall()

        if format.lower() == "csv":
            # 返回CSV格式
            import csv
            from io import StringIO
            from fastapi.responses import Response

            output = StringIO()
            writer = csv.writer(output)

            # 写入表头
            writer.writerow([
                "ID", "客户名称", "是否新客户", "归属类型", "区域", "地址", "联系人", "联系方式",
                "状态", "合同开始", "合同结束", "属性", "类型", "面积", "会员数", "教练数", "新增教练数",
                "备注", "计划", "签到照片", "创建人", "创建时间", "纬度", "经度"
            ])

            # 写入数据
            for customer in customers:
                writer.writerow([
                    customer['id'],
                    customer['name'] or '',
                    '是' if customer['is_new'] else '否',
                    customer['ownership'] or '',
                    customer['region'] or '',
                    customer['address'] or '',
                    customer['contact_person'] or '',
                    customer['contact_method'] or '',
                    customer['status'] or '',
                    customer['contract_start_month'].strftime("%Y-%m") if customer['contract_start_month'] else '',
                    customer['contract_end_month'].strftime("%Y-%m") if customer['contract_end_month'] else '',
                    customer['attribute'] or '',
                    customer['type'] or '',
                    customer['area'] or '',
                    customer['member_count'] or '',
                    customer['coach_count'] or '',
                    customer['added_coach_count'] or '',
                    customer['remarks'] or '',
                    customer['plan'] or '',
                    customer['check_in_photo'] or '',
                    customer['created_by'] or '',
                    customer['created_at'].strftime("%Y-%m-%d %H:%M:%S") if customer['created_at'] else '',
                    customer['lat'] or '',
                    customer['lng'] or ''
                ])

            output.seek(0)
            return Response(
                content=output.getvalue().encode('utf-8-sig'),  # 使用utf-8-sig以支持Excel正确显示中文
                media_type="text/csv",
                headers={
                    "Content-Disposition": f"attachment; filename=customers_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
                }
            )
        else:
            # 返回JSON格式
            formatted_customers = []
            for customer in customers:
                formatted_customer = {
                    "id": str(customer['id']),
                    "name": customer['name'],
                    "isNew": bool(customer['is_new']),
                    "ownership": customer['ownership'],
                    "region": customer['region'],
                    "address": customer['address'],
                    "contactPerson": customer['contact_person'],
                    "contactMethod": customer['contact_method'],
                    "status": customer['status'],
                    "contractStartMonth": customer['contract_start_month'].strftime("%Y-%m") if customer[
                        'contract_start_month'] else None,
                    "contractEndMonth": customer['contract_end_month'].strftime("%Y-%m") if customer[
                        'contract_end_month'] else None,
                    "attribute": customer['attribute'],
                    "type": customer['type'],
                    "area": customer['area'],
                    "memberCount": customer['member_count'],
                    "coachCount": customer['coach_count'],
                    "addedCoachCount": customer['added_coach_count'],
                    "remarks": customer['remarks'],
                    "plan": customer['plan'],
                    "checkInPhoto": customer['check_in_photo'],
                    "createdBy": customer['created_by'],
                    "createdAt": customer['created_at'].strftime("%Y-%m-%d %H:%M:%S") if customer[
                        'created_at'] else None,
                    "lat": float(customer['lat']) if customer.get('lat') is not None else None,
                    "lng": float(customer['lng']) if customer.get('lng') is not None else None
                }
                formatted_customers.append(formatted_customer)

            cursor.close()
            conn.close()

            return create_wrapped_response(
                data={
                    "list": formatted_customers,
                    "total": len(formatted_customers)
                },
                message="success",
                status=200
            )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"导出客户数据失败: {str(e)}")


class ApiCustomerUpdate(BaseModel):
    name: Optional[str] = None
    isNew: Optional[bool] = None
    ownership: Optional[str] = None
    region: Optional[str] = None
    address: Optional[str] = None
    contactPerson: Optional[str] = None
    contactMethod: Optional[str] = None
    status: Optional[str] = None
    contractPeriod: Optional[dict] = None
    attribute: Optional[str] = None
    type: Optional[str] = None
    area: Optional[int] = None
    memberCount: Optional[int] = None
    coachCount: Optional[int] = None
    addedCoachCount: Optional[int] = None
    storeActions: Optional[dict] = None
    actionValues: Optional[dict] = None
    remarks: Optional[str] = None
    plan: Optional[str] = None
    lat: Optional[float] = None  # 纬度
    lng: Optional[float] = None  # 经度


class ApiHistoryCreate(BaseModel):
    visitTime: str
    status: str
    area: Optional[int] = None
    memberCount: Optional[int] = None
    coachCount: Optional[int] = None
    addedCoachCount: Optional[int] = None
    followUpPerson: str
    checkInPhoto: Optional[str] = None


class ApiBatchRequest(BaseModel):
    action: str
    customer_ids: List[str]
    data: Optional[dict] = None


# ==================== 客户历史记录接口 ====================

@app.get("/api/customers/{cust_id}/history", response_model=WrappedApiResponse)
async def api_get_customer_history(
        cust_id: str,
        page: int = Query(1, ge=1),
        page_size: int = Query(10, ge=1, le=100)
):
    """
    获取客户历史记录接口
    文档来源: 千人千店客户管理API文档.md
    操作表: customer_history, customers
    功能: 获取指定客户的历史访问记录，支持分页查询
    参数:
        - cust_id: 客户ID（字符串类型）
        - page: 页码，默认1，最小值为1
        - page_size: 每页数量，默认10，范围1-100
    返回: 分页的客户历史记录列表，包含访问时间、客户信息、状态等
    返回格式: {
        "data": {
            "list": [...],
            "pagination": {"page": 1, "page_size": 10, "total": 100, "total_pages": 10}
        }
    }
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        cursor.execute("SELECT COUNT(*) AS total FROM customer_history WHERE customer_id = %s", (cust_id,))
        total = cursor.fetchone()["total"]

        cursor.execute(
            """
            SELECT h.visit_time, c.name, c.region, c.address, c.contact_person, c.contact_method,
                   c.attribute, c.type, h.status, h.area, h.member_count, h.coach_count,
                   h.added_coach_count, h.follow_up_person, h.check_in_photo
            FROM customer_history h
            LEFT JOIN customers c ON h.customer_id = c.id
            WHERE h.customer_id = %s
            ORDER BY h.visit_time DESC
            LIMIT %s OFFSET %s
            """,
            (cust_id, page_size, (page - 1) * page_size)
        )
        rows = cursor.fetchall()

        data = [
            {
                "visitTime": r["visit_time"].strftime("%Y-%m-%d") if r["visit_time"] else None,
                "name": r["name"],
                "region": r["region"],
                "address": r["address"],
                "contactPerson": r["contact_person"],
                "contactMethod": r["contact_method"],
                "attribute": r["attribute"],
                "type": r["type"],
                "status": r["status"],
                "area": r["area"],
                "memberCount": r["member_count"],
                "coachCount": r["coach_count"],
                "addedCoachCount": r["added_coach_count"],
                "followUpPerson": r["follow_up_person"],
                "checkInPhoto": r["check_in_photo"]
            }
            for r in rows
        ]

        result = {
            "list": data,
            "pagination": {
                "page": page,
                "page_size": page_size,
                "total": total,
                "total_pages": (total + page_size - 1) // page_size
            }
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=result, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取客户历史失败: {str(e)}")


@app.post("/api/customers/{cust_id}/history", response_model=WrappedApiResponse)
async def api_create_customer_history(cust_id: str, req: ApiHistoryCreate):
    """
    添加客户历史记录接口
    文档来源: 千人千店客户管理API文档.md
    操作表: customer_history, customers
    功能: 为客户添加一条历史访问记录，并更新客户的最后拜访时间
    参数:
        - cust_id: 客户ID（字符串类型）
        - req: 历史记录信息（访问时间、状态、面积、会员数、教练数等）
    返回: 创建成功消息
    返回格式: {"data": {}, "message": "历史记录添加成功", "status": 201}
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 检查客户是否存在
        cursor.execute("SELECT id FROM customers WHERE id = %s", (cust_id,))
        if not cursor.fetchone():
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="客户不存在", status=404)

        cursor.execute(
            """
            INSERT INTO customer_history 
            (customer_id, visit_time, status, area, member_count, coach_count, added_coach_count, follow_up_person, check_in_photo)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            """,
            (
                cust_id, req.visitTime, req.status, req.area, req.memberCount,
                req.coachCount, req.addedCoachCount, req.followUpPerson, req.checkInPhoto
            )
        )

        # 更新客户的最后拜访时间
        cursor.execute("UPDATE customers SET last_visit_time = %s WHERE id = %s", (req.visitTime, cust_id))
        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(data={}, message="历史记录添加成功", status=201)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"添加历史记录失败: {str(e)}")


# ==================== 客户区域接口 ====================

@app.get("/api/customers/regions", response_model=WrappedApiResponse)
async def api_get_customer_regions():
    """
    获取客户区域列表接口（省市区三级联动）
    文档来源: 客户管理-区域数据接口需求.md
    操作表: customers
    功能: 从customers表的region字段解析省市区三级数据，只返回有跟进人的客户区域（owner != 0）
    参数: 无
    返回: 省市区三级嵌套数据结构
    返回格式: {
        "code": 200,
        "message": "success",
        "data": {
            "四川省": {
                "成都市": ["锦江区", "青羊区", "金牛区"],
                "绵阳市": ["涪城区", "游仙区"]
            },
            "安徽省": {
                "合肥市": ["包河区", "庐阳区", "瑶海区"]
            }
        }
    }
    业务逻辑: 只查询有跟进人的客户数据（owner != 0），排除商机数据（owner = 0）
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 只查询有跟进人的客户数据（owner != 0），排除商机数据（owner = 0）
        cursor.execute("""
            SELECT DISTINCT region 
            FROM customers 
            WHERE owner IS NOT NULL 
              AND owner != 0 
              AND region IS NOT NULL 
              AND region != ''
        """)
        rows = cursor.fetchall()

        # 构建三级嵌套结构：{ 省份: { 城市: [区县] } }
        region_map: dict[str, dict[str, list[str]]] = {}

        for row in rows:
            if not row or "region" not in row or not row["region"]:
                continue
            region_str = str(row["region"]).strip()
            if not region_str:
                continue

            # 支持两种格式：/ 分隔 或 空格分隔
            if '/' in region_str:
                parts = [p.strip() for p in region_str.split('/') if p.strip()]
            else:
                parts = [p.strip() for p in region_str.split() if p.strip()]

            if len(parts) >= 1:
                province = parts[0]
                city = parts[1] if len(parts) >= 2 else ''
                district = parts[2] if len(parts) >= 3 else ''

                if province:
                    # 初始化省份
                    if province not in region_map:
                        region_map[province] = {}

                    if city:
                        # 初始化城市
                        if city not in region_map[province]:
                            region_map[province][city] = []

                        # 添加区县（去重）
                        if district and district not in region_map[province][city]:
                            region_map[province][city].append(district)

        # 排序：省份、城市、区县都需要排序
        sorted_region_map = {}
        for province in sorted(region_map.keys(), key=lambda x: x):
            sorted_region_map[province] = {}
            for city in sorted(region_map[province].keys(), key=lambda x: x):
                sorted_region_map[province][city] = sorted(region_map[province][city])

        cursor.close()
        conn.close()
        
        return create_wrapped_response(
            data=sorted_region_map,
            message="success",
            status=200
        )
    except Exception as e:
        import traceback
        error_detail = f"获取区域列表失败: {str(e)}\n{traceback.format_exc()}"
        print(f"[ERROR] {error_detail}")
        # 如果出错，返回空数据而不是500错误，避免前端崩溃
        return create_wrapped_response(
            data={},  # 返回空对象，符合三级嵌套结构格式 { 省份: { 城市: [区县] } }
            message="success",
            status=200
        )


@app.get("/api/customers/statistics", response_model=WrappedApiResponse)
async def api_get_customer_statistics(
        province: Optional[str] = Query(None),
        city: Optional[str] = Query(None),
        district: Optional[str] = Query(None),
        date_range: Optional[str] = Query(None)
):
    """
    获取客户统计信息接口
    文档来源: 千人千店客户管理API文档.md
    操作表: customers
    功能: 获取客户的统计信息，支持按省市区和日期范围筛选
    参数:
        - province: 省份筛选（可选）
        - city: 城市筛选（可选）
        - district: 区县筛选（可选）
        - date_range: 日期范围筛选，格式为"开始日期,结束日期"（可选）
    返回: 客户统计数据，包括总数、新客户数、状态分布、属性分布、类型分布、归属类型分布、区域分布
    返回格式: {
        "data": {
            "total": 100,
            "newCustomers": 20,
            "statusDistribution": {"已签约": 50, "意向": 30, ...},
            "attributeDistribution": {"商圈": 40, "社区": 60, ...},
            "typeDistribution": {"商健": 70, "工作室": 30, ...},
            "ownershipDistribution": {"直营": 80, "加盟": 20, ...},
            "regionDistribution": {"湖南省 长沙市 岳麓区": 30, ...}
        }
    }
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params = []

        if province:
            where.append("region LIKE %s")
            params.append(f"%{province}%")
        if city:
            where.append("region LIKE %s")
            params.append(f"%{city}%")
        if district:
            where.append("region LIKE %s")
            params.append(f"%{district}%")
        if date_range:
            parts = date_range.split(",")
            if len(parts) == 2:
                where.append("created_at BETWEEN %s AND %s")
                params.extend([parts[0], parts[1]])

        where_clause = " AND ".join(where) if where else "1=1"

        # 总数
        cursor.execute(f"SELECT COUNT(*) AS cnt FROM customers WHERE {where_clause}", params)
        total = cursor.fetchone()["cnt"]

        # 新客户数
        cursor.execute(f"SELECT COUNT(*) AS cnt FROM customers WHERE is_new = 1 AND {where_clause}", params)
        new_customers = cursor.fetchone()["cnt"]

        # 状态分布
        cursor.execute(f"SELECT status, COUNT(*) AS cnt FROM customers WHERE {where_clause} GROUP BY status", params)
        status_dist = {r["status"] or "": r["cnt"] for r in cursor.fetchall()}

        # 属性分布
        cursor.execute(f"SELECT attribute, COUNT(*) AS cnt FROM customers WHERE {where_clause} GROUP BY attribute",
                       params)
        attr_dist = {r["attribute"] or "": r["cnt"] for r in cursor.fetchall()}

        # 类型分布
        cursor.execute(f"SELECT type, COUNT(*) AS cnt FROM customers WHERE {where_clause} GROUP BY type", params)
        type_dist = {r["type"] or "": r["cnt"] for r in cursor.fetchall()}

        # 归属类型分布
        cursor.execute(f"SELECT ownership, COUNT(*) AS cnt FROM customers WHERE {where_clause} GROUP BY ownership",
                       params)
        ownership_dist = {r["ownership"] or "": r["cnt"] for r in cursor.fetchall()}

        # 区域分布
        cursor.execute(f"SELECT region, COUNT(*) AS cnt FROM customers WHERE {where_clause} GROUP BY region", params)
        region_dist = {r["region"] or "": r["cnt"] for r in cursor.fetchall()}

        data = {
            "total": total,
            "newCustomers": new_customers,
            "statusDistribution": status_dist,
            "attributeDistribution": attr_dist,
            "typeDistribution": type_dist,
            "ownershipDistribution": ownership_dist,
            "regionDistribution": region_dist
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取客户统计失败: {str(e)}")


@app.post("/api/customers/batch", response_model=WrappedApiResponse)
async def api_customers_batch(req: ApiBatchRequest):
    """
    批量操作客户接口
    文档来源: 千人千店客户管理API文档.md
    操作表: customers
    功能: 对多个客户进行批量操作（更新状态、删除、导出等）
    参数:
        - req: 批量操作请求，包含操作类型（action）、客户ID列表（customer_ids）、操作数据（data）
    支持的操作类型:
        - "update_status": 批量更新客户状态
        - "delete": 批量删除客户
        - "export": 导出客户（不实际删除）
    返回: 批量操作结果，包含成功数量、失败数量和失败项列表
    返回格式: {
        "data": {
            "success_count": 10,
            "failed_count": 2,
            "failed_items": [{"id": "123", "error": "错误信息"}, ...]
        }
    }
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        success_count = 0
        failed_count = 0
        failed_items = []

        for cust_id in req.customer_ids:
            try:
                if req.action == "update_status" and req.data and req.data.get("status"):
                    cursor.execute("UPDATE customers SET status = %s WHERE id = %s", (req.data["status"], cust_id))
                elif req.action == "delete":
                    cursor.execute("DELETE FROM customers WHERE id = %s", (cust_id,))
                elif req.action == "export":
                    # 导出操作不需要实际删除，这里跳过
                    pass
                else:
                    failed_count += 1
                    failed_items.append({"id": cust_id, "error": "未知操作"})
                    continue
                success_count += 1
            except Exception as e:
                failed_count += 1
                failed_items.append({"id": cust_id, "error": str(e)})

        conn.commit()
        cursor.close()
        conn.close()

        data = {
            "success_count": success_count,
            "failed_count": failed_count,
            "failed_items": failed_items
        }
        return create_wrapped_response(data=data, message="批量操作成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"批量操作失败: {str(e)}")


# ========== 用户管理和权限管理（无鉴权、无userid）- 用户管理和权限管理API文档.md ==========

def check_circular_reference(conn, user_id: int, manager_id: Optional[int]) -> bool:
    """
    检测设置上级后是否形成循环引用
    返回True表示存在循环引用，False表示不存在
    """
    if not manager_id:
        return False

    current_manager_id = manager_id
    visited = set()

    while current_manager_id:
        if current_manager_id == user_id:
            # 形成循环引用
            return True
        if current_manager_id in visited:
            # 检测到循环
            return True
        visited.add(current_manager_id)

        # 获取当前上级的上级
        cursor = conn.cursor(_DictCursorAlias)
        cursor.execute("SELECT manager_id FROM users WHERE id = %s", (current_manager_id,))
        row = cursor.fetchone()
        cursor.close()

        if not row or not row.get("manager_id"):
            break
        current_manager_id = row["manager_id"]

    return False


class UserCreate(BaseModel):
    account: str
    name: str
    password: str
    role: str
    organization: str
    manager_id: Optional[int] = None
    status: Optional[str] = "enabled"


class UserUpdate(BaseModel):
    account: Optional[str] = None
    name: Optional[str] = None
    newPassword: Optional[str] = None
    role: Optional[str] = None
    organization: Optional[str] = None
    manager_id: Optional[int] = None
    status: Optional[str] = None


class UserResetPassword(BaseModel):
    newPassword: str


class UserToggleStatus(BaseModel):
    status: str


class RoleCreate(BaseModel):
    name: str
    dataScope: str
    description: Optional[str] = None
    editPermission: str
    status: Optional[str] = "active"
    permissions: Optional[List[int]] = None


class RoleUpdate(BaseModel):
    name: Optional[str] = None
    dataScope: Optional[str] = None
    description: Optional[str] = None
    editPermission: Optional[str] = None
    status: Optional[str] = None
    permissions: Optional[List[int]] = None


class OrganizationCreate(BaseModel):
    name: str
    code: Optional[str] = None  # 改为可选，如果前端不传则由后端自动生成
    parentId: Optional[int] = 0
    region: Union[str, List[str]]  # 支持字符串或数组格式: "广东省/深圳市" 或 ["广东省/深圳市", "北京市/北京市/全部"]
    type: str  # 组织类型，可选值: sales(销售部门), support(支持部门), admin(管理部门), warehouse(仓库部门)
    description: Optional[str] = None
    status: Optional[str] = "active"


class OrganizationUpdate(BaseModel):
    name: Optional[str] = None
    code: Optional[str] = None
    parentId: Optional[int] = None
    region: Optional[Union[str, List[str]]] = None  # 支持字符串或数组格式
    type: Optional[str] = None
    description: Optional[str] = None
    status: Optional[str] = None


class RolePermissionsRequest(BaseModel):
    permissionIds: List[int]


# ==================== 用户管理接口（无鉴权、无userid）- 用户管理和权限管理API文档.md ====================

@app.get("/api/users", response_model=WrappedApiResponse)
async def api_get_users(
        page: int = Query(1, ge=1),
        page_size: int = Query(20, ge=1, le=100),
        search: Optional[str] = Query(None),
        role: Optional[str] = Query(None),
        organization: Optional[str] = Query(None),
        status: Optional[str] = Query(None)
):
    """
    获取用户列表接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users, roles
    功能: 获取用户列表，支持分页和多条件筛选（搜索、角色、组织、状态）
    参数:
        - page: 页码，默认1，最小值为1
        - page_size: 每页数量，默认20，范围1-100
        - search: 搜索关键词，匹配账号或姓名（可选）
        - role: 角色筛选（可选）
        - organization: 组织筛选（可选）
        - status: 状态筛选（可选）
    返回: 分页的用户列表，包含用户基本信息、角色、组织、状态等
    返回格式: {
        "data": {
            "list": [{"id": 1, "account": "user1", "name": "用户1", "role": "admin", ...}, ...],
            "pagination": {"page": 1, "page_size": 20, "total": 100, "total_pages": 5}
        }
    }
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params = []

        if search:
            where.append("(u.account LIKE %s OR u.name LIKE %s)")
            like = f"%{search}%"
            params.extend([like, like])
        if role:
            where.append("u.role = %s")
            params.append(role)
        if organization:
            where.append("u.department = %s")
            params.append(organization)
        if status:
            where.append("u.status = %s")
            params.append(status)

        where_clause = " AND ".join(where) if where else "1=1"

        cursor.execute(f"SELECT COUNT(*) AS cnt FROM users u WHERE {where_clause}", params)
        total = cursor.fetchone()["cnt"]

        cursor.execute(
            f"""
            SELECT u.id, u.account, u.name, u.avatar, u.role, u.department, u.status, 
                   u.created_at, u.updated_at, u.manager_id, m.name AS manager_name
            FROM users u
            LEFT JOIN users m ON u.manager_id = m.id
            WHERE {where_clause}
            ORDER BY u.id DESC
            LIMIT %s OFFSET %s
            """,
            params + [page_size, (page - 1) * page_size]
        )
        rows = cursor.fetchall()

        data = []
        for r in rows:
            # 获取roleId（从roles表）
            role_id = 0
            if r["role"]:
                cursor.execute("SELECT id FROM roles WHERE name = %s", (r["role"],))
                role_row = cursor.fetchone()
                if role_row:
                    role_id = role_row["id"]

            data.append({
                "id": r["id"],
                "account": r["account"],
                "name": r["name"],
                "avatar": r.get("avatar"),
                "role": r["role"],
                "roleId": role_id,
                "organization": r.get("department", ""),
                "organizationId": 0,
                "manager_id": r.get("manager_id"),
                "manager_name": r.get("manager_name"),
                "status": r.get("status", "enabled"),
                "isSystemAccount": False,
                "created_at": r["created_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if r["created_at"] else None,
                "updated_at": r["updated_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if r["updated_at"] else None
            })

        result = {
            "list": data,
            "pagination": {
                "page": page,
                "page_size": page_size,
                "total": total,
                "total_pages": (total + page_size - 1) // page_size
            }
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=result, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户列表失败: {str(e)}")


@app.get("/api/users/{user_id}", response_model=WrappedApiResponse)
async def api_get_user_detail(user_id: int):
    """
    获取用户详情接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users, roles
    功能: 根据用户ID获取用户详细信息
    参数:
        - user_id: 用户ID（整数类型）
    返回: 用户详细信息，包含账号、姓名、头像、角色、组织、状态、创建时间、更新时间等
    返回格式: {
        "data": {
            "id": 1,
            "account": "user1",
            "name": "用户1",
            "avatar": "https://...",
            "role": "admin",
            "roleId": 1,
            "organization": "技术部",
            "organizationId": 1,
            "status": "enabled",
            "isSystemAccount": false,
            "created_at": "2024-01-01T00:00:00.000Z",
            "updated_at": "2024-01-01T00:00:00.000Z"
        }
    }
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        cursor.execute(
            """
            SELECT u.id, u.account, u.name, u.avatar, u.role, u.department, u.status, 
                   u.created_at, u.updated_at, u.manager_id, m.name AS manager_name
            FROM users u
            LEFT JOIN users m ON u.manager_id = m.id
            WHERE u.id = %s
            """,
            (user_id,)
        )
        r = cursor.fetchone()
        cursor.close()
        conn.close()

        if not r:
            return create_wrapped_response(data=None, message="用户不存在", status=404)

        role_id = 0
        if r["role"]:
            conn2 = get_db_connection()
            cursor2 = conn2.cursor(_DictCursorAlias)
            cursor2.execute("SELECT id FROM roles WHERE name = %s", (r["role"],))
            role_row = cursor2.fetchone()
            if role_row:
                role_id = role_row["id"]
            cursor2.close()
            conn2.close()

        data = {
            "id": r["id"],
            "account": r["account"],
            "name": r["name"],
            "avatar": r.get("avatar"),
            "role": r["role"],
            "roleId": role_id,
            "organization": r.get("department", ""),
            "organizationId": 0,
            "manager_id": r.get("manager_id"),
            "manager_name": r.get("manager_name"),
            "status": r.get("status", "enabled"),
            "isSystemAccount": False,
            "created_at": r["created_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if r["created_at"] else None,
            "updated_at": r["updated_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if r["updated_at"] else None
        }
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取用户详情失败: {str(e)}")


@app.post("/api/users", response_model=WrappedApiResponse)
async def api_create_user(req: UserCreate):
    """
    创建用户接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users
    功能: 创建新用户，自动生成头像URL
    参数:
        - req: 用户创建信息（账号、姓名、密码、角色、组织、状态）
    返回: 创建成功的用户ID
    返回格式: {"data": {"id": 1}, "message": "用户创建成功", "status": 200}
    注意:
        - 密码以明文存储（建议后续加密）
        - 头像URL自动从dicebear.com生成
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 验证manager_id（如果提供）
        manager_id = req.manager_id
        manager_name = None
        if manager_id is not None:
            # 检查上级用户是否存在
            cursor.execute("SELECT id, name, status FROM users WHERE id = %s", (manager_id,))
            manager_row = cursor.fetchone()
            if not manager_row:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="上级用户不存在", status=400)

            # 检查上级用户状态是否为启用
            if manager_row["status"] != "enabled":
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="上级用户状态不是启用", status=400)

            manager_name = manager_row["name"]

        # 生成头像URL
        import urllib.parse
        avatar = f"https://api.dicebear.com/7.x/avataaars/svg?seed={urllib.parse.quote(req.name)}"

        cursor.execute(
            """
            INSERT INTO users (account, name, password, role, department, status, avatar, manager_id)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
            """,
            (req.account, req.name, req.password, req.role, req.organization, req.status, avatar, manager_id)
        )
        user_id = cursor.lastrowid

        # 获取创建的用户信息用于返回
        cursor.execute(
            """
            SELECT id, account, name, role, department, manager_id, status, created_at, updated_at
            FROM users WHERE id = %s
            """,
            (user_id,)
        )
        user_row = cursor.fetchone()

        conn.commit()
        cursor.close()
        conn.close()

        # 返回创建的用户信息，包含manager_id和manager_name
        return create_wrapped_response(
            data={
                "id": user_row["id"],
                "account": user_row["account"],
                "name": user_row["name"],
                "role": user_row["role"],
                "organization": user_row["department"],
                "manager_id": user_row.get("manager_id"),
                "manager_name": manager_name,
                "status": user_row["status"],
                "created_at": user_row["created_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if user_row["created_at"] else None,
                "updated_at": user_row["updated_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if user_row["updated_at"] else None
            },
            message="用户创建成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建用户失败: {str(e)}")


@app.put("/api/users/{user_id}", response_model=WrappedApiResponse)
async def api_update_user(user_id: int, req: UserUpdate):
    """
    更新用户接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users
    功能: 更新用户信息（账号、姓名、密码、角色、组织、状态）
    参数:
        - user_id: 用户ID（整数类型）
        - req: 用户更新信息（所有字段都是可选的）
    返回: 更新成功消息
    返回格式: {"data": {}, "message": "用户信息更新成功", "status": 200}
    注意:
        - 如果用户不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
        if not cursor.fetchone():
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="用户不存在", status=404)

        # 验证manager_id（如果提供）
        manager_id = req.manager_id
        manager_name = None
        if manager_id is not None:
            # 检查不能将自己设置为上级
            if manager_id == user_id:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="不能将自己设置为上级", status=400)

            # 检查上级用户是否存在
            cursor.execute("SELECT id, name, status FROM users WHERE id = %s", (manager_id,))
            manager_row = cursor.fetchone()
            if not manager_row:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="上级用户不存在", status=400)

            # 检查上级用户状态是否为启用
            if manager_row["status"] != "enabled":
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="上级用户状态不是启用", status=400)

            # 检查循环引用
            if check_circular_reference(conn, user_id, manager_id):
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="检测到循环引用", status=400)

            manager_name = manager_row["name"]
        elif req.manager_id is not None and req.manager_id == 0:
            # 如果传递0或明确传递null，表示清除上级
            manager_id = None

        update_fields = []
        params = []

        if req.account is not None:
            update_fields.append("account = %s")
            params.append(req.account)
        if req.name is not None:
            update_fields.append("name = %s")
            params.append(req.name)
        if req.newPassword is not None:
            update_fields.append("password = %s")
            params.append(req.newPassword)
        if req.role is not None:
            update_fields.append("role = %s")
            params.append(req.role)
        if req.organization is not None:
            update_fields.append("department = %s")
            params.append(req.organization)
        if req.manager_id is not None:
            update_fields.append("manager_id = %s")
            params.append(manager_id)
        if req.status is not None:
            update_fields.append("status = %s")
            params.append(req.status)

        if update_fields:
            params.append(user_id)
            cursor.execute(f"UPDATE users SET {', '.join(update_fields)} WHERE id = %s", params)
            conn.commit()

        # 获取更新后的用户信息用于返回
        cursor.execute(
            """
            SELECT u.id, u.account, u.name, u.role, u.department, u.manager_id, u.status, u.updated_at, m.name AS manager_name
            FROM users u
            LEFT JOIN users m ON u.manager_id = m.id
            WHERE u.id = %s
            """,
            (user_id,)
        )
        user_row = cursor.fetchone()

        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={
                "id": user_row["id"],
                "account": user_row["account"],
                "name": user_row["name"],
                "role": user_row["role"],
                "organization": user_row["department"],
                "manager_id": user_row.get("manager_id"),
                "manager_name": user_row.get("manager_name"),
                "status": user_row["status"],
                "updated_at": user_row["updated_at"].strftime("%Y-%m-%dT%H:%M:%S.000Z") if user_row["updated_at"] else None
            },
            message="用户信息更新成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新用户失败: {str(e)}")


@app.delete("/api/users/{user_id}", response_model=WrappedApiResponse)
async def api_delete_user(user_id: int):
    """
    删除用户接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users
    功能: 根据用户ID删除用户
    参数:
        - user_id: 用户ID（整数类型）
    返回: 删除成功消息
    返回格式: {"data": None, "message": "用户删除成功", "status": 200}
    注意:
        - 如果用户不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        affected = cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
        conn.commit()
        cursor.close()
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="用户不存在", status=404)
        return create_wrapped_response(data=None, message="用户删除成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除用户失败: {str(e)}")


@app.post("/api/users/{user_id}/reset-password", response_model=WrappedApiResponse)
async def api_reset_user_password(user_id: int, req: UserResetPassword):
    """
    重置用户密码接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users
    功能: 重置指定用户的密码
    参数:
        - user_id: 用户ID（整数类型）
        - req: 新密码信息（newPassword字段）
    返回: 重置成功消息
    返回格式: {"data": None, "message": "密码重置成功", "status": 200}
    注意:
        - 如果用户不存在，返回404错误
        - 密码以明文存储（建议后续加密）
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        affected = cursor.execute("UPDATE users SET password = %s WHERE id = %s", (req.newPassword, user_id))
        conn.commit()
        cursor.close()
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="用户不存在", status=404)
        return create_wrapped_response(data=None, message="密码重置成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"重置密码失败: {str(e)}")


@app.post("/api/users/{user_id}/toggle-status", response_model=WrappedApiResponse)
async def api_toggle_user_status(user_id: int, req: UserToggleStatus):
    """
    切换用户状态接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: users
    功能: 切换用户的启用/禁用状态
    参数:
        - user_id: 用户ID（整数类型）
        - req: 状态信息（status字段，值为"enabled"或"disabled"）
    返回: 状态更新成功消息和用户状态
    返回格式: {
        "data": {"id": 1, "status": "enabled"},
        "message": "用户状态已更新为启用",
        "status": 200
    }
    注意:
        - 如果用户不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        affected = cursor.execute("UPDATE users SET status = %s WHERE id = %s", (req.status, user_id))
        conn.commit()
        cursor.close()
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="用户不存在", status=404)
        return create_wrapped_response(data={"id": user_id, "status": req.status},
                                       message=f"用户状态已更新为{'启用' if req.status == 'enabled' else '禁用'}",
                                       status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"切换用户状态失败: {str(e)}")


# ==================== 角色管理接口 ====================

@app.get("/api/roles", response_model=WrappedApiResponse)
async def api_get_roles(
        page: int = Query(1, ge=1),
        page_size: int = Query(10, ge=1, le=100),
        search: Optional[str] = Query(None),
        status: Optional[str] = Query(None)
):
    """
    获取角色列表接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, users
    功能: 获取角色列表，支持分页和条件筛选（搜索、状态），并统计每个角色的用户数
    参数:
        - page: 页码，默认1，最小值为1
        - page_size: 每页数量，默认10，范围1-100
        - search: 搜索关键词，匹配角色名称（可选）
        - status: 状态筛选（可选）
    返回: 分页的角色列表，包含角色基本信息、数据范围、描述、状态、用户数、创建时间、更新时间
    返回格式: {
        "data": {
            "list": [{"id": 1, "name": "管理员", "dataScope": "全部数据", "userCount": 5, ...}, ...],
            "pagination": {"page": 1, "page_size": 10, "total": 10, "total_pages": 1}
        }
    }
    特殊处理: 如果roles表不存在，返回空列表而不是错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params = []
        if search:
            where.append("name LIKE %s")
            params.append(f"%{search}%")
        if status:
            where.append("status = %s")
            params.append(status)

        where_clause = " AND ".join(where) if where else "1=1"

        try:
            cursor.execute(f"SELECT COUNT(*) AS cnt FROM roles WHERE {where_clause}", params)
            total = cursor.fetchone()["cnt"]

            cursor.execute(
                f"""
                SELECT id, name, data_scope, description, edit_permission, status, created_at, updated_at
                FROM roles
                WHERE {where_clause}
                ORDER BY id DESC
                LIMIT %s OFFSET %s
                """,
                params + [page_size, (page - 1) * page_size]
            )
            rows = cursor.fetchall()

            data = []
            for r in rows:
                # 统计使用该角色的用户数
                cursor.execute("SELECT COUNT(*) AS cnt FROM users WHERE role = %s", (r["name"],))
                user_count = cursor.fetchone()["cnt"]

                data.append({
                    "id": r["id"],
                    "name": r["name"],
                    "dataScope": r.get("data_scope", "仅本人数据"),
                    "description": r.get("description"),
                    "editPermission": r.get("edit_permission", "enabled"),
                    "status": r.get("status", "active"),
                    "userCount": user_count,
                    "createTime": r["created_at"].strftime("%Y-%m-%d") if r["created_at"] else None,
                    "updateTime": r["updated_at"].strftime("%Y-%m-%d") if r["updated_at"] else None
                })
        except Exception:
            total = 0
            data = []

        result = {
            "list": data,
            "pagination": {
                "page": page,
                "page_size": page_size,
                "total": total,
                "total_pages": (total + page_size - 1) // page_size
            }
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=result, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取角色列表失败: {str(e)}")


@app.get("/api/roles/{role_id}", response_model=WrappedApiResponse)
async def api_get_role_detail(role_id: int):
    """
    获取角色详情接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, users, role_permissions, permissions
    功能: 根据角色ID获取角色详细信息，包括角色权限列表
    参数:
        - role_id: 角色ID（整数类型）
    返回: 角色详细信息，包含基本信息、用户数、权限列表
    返回格式: {
        "data": {
            "id": 1,
            "name": "管理员",
            "dataScope": "全部数据",
            "description": "系统管理员",
            "status": "active",
            "userCount": 5,
            "permissions": [{"id": 1, "name": "用户管理", "code": "user:manage", "type": "menu"}, ...],
            "createTime": "2024-01-01",
            "updateTime": "2024-01-01"
        }
    }
    特殊处理: 如果角色不存在或roles表不存在，返回404错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        try:
            cursor.execute(
                "SELECT id, name, data_scope, description, edit_permission, status, created_at, updated_at FROM roles WHERE id = %s",
                (role_id,)
            )
            r = cursor.fetchone()

            if not r:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="角色不存在", status=404)

            cursor.execute("SELECT COUNT(*) AS cnt FROM users WHERE role = %s", (r["name"],))
            user_count = cursor.fetchone()["cnt"]

            # 获取角色权限
            permissions = []
            try:
                cursor.execute(
                    "SELECT permission_id FROM role_permissions WHERE role_id = %s",
                    (role_id,)
                )
                perm_rows = cursor.fetchall()
                for pr in perm_rows:
                    cursor.execute("SELECT id, name, code, type FROM permissions WHERE id = %s", (pr["permission_id"],))
                    p = cursor.fetchone()
                    if p:
                        permissions.append({
                            "id": p["id"],
                            "name": p["name"],
                            "code": p["code"],
                            "type": p["type"]
                        })
            except Exception:
                pass

            data = {
                "id": r["id"],
                "name": r["name"],
                "dataScope": r.get("data_scope", "仅本人数据"),
                "description": r.get("description"),
                "editPermission": r.get("edit_permission", "enabled"),
                "status": r.get("status", "active"),
                "userCount": user_count,
                "permissions": permissions,
                "createTime": r["created_at"].strftime("%Y-%m-%d") if r["created_at"] else None,
                "updateTime": r["updated_at"].strftime("%Y-%m-%d") if r["updated_at"] else None
            }
        except Exception:
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="角色不存在", status=404)

        cursor.close()
        conn.close()
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取角色详情失败: {str(e)}")


@app.post("/api/roles", response_model=WrappedApiResponse)
async def api_create_role(req: RoleCreate):
    """
    创建角色接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, role_permissions
    功能: 创建新角色，并设置角色权限
    参数:
        - req: 角色创建信息（名称、数据范围、描述、状态、权限ID列表）
    返回: 创建成功的角色ID
    返回格式: {"data": {"id": 1}, "message": "角色创建成功", "status": 200}
    注意:
        - 如果提供了权限列表，会自动创建role_permissions关联记录
        - 权限创建失败不会影响角色创建
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        # 验证editPermission字段
        edit_permission = req.editPermission
        if edit_permission not in ["enabled", "disabled"]:
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="编辑权限值无效，必须为 enabled 或 disabled", status=400)

        cursor.execute(
            """
            INSERT INTO roles (name, data_scope, description, edit_permission, status)
            VALUES (%s, %s, %s, %s, %s)
            """,
            (req.name, req.dataScope, req.description, edit_permission, req.status)
        )
        role_id = cursor.lastrowid

        # 设置权限
        if req.permissions:
            for perm_id in req.permissions:
                try:
                    cursor.execute(
                        "INSERT INTO role_permissions (role_id, permission_id) VALUES (%s, %s)",
                        (role_id, perm_id)
                    )
                except Exception:
                    pass

        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(data={"id": role_id}, message="角色创建成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建角色失败: {str(e)}")


@app.put("/api/roles/{role_id}", response_model=WrappedApiResponse)
async def api_update_role(role_id: int, req: RoleUpdate):
    """
    更新角色接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, role_permissions
    功能: 更新角色信息（名称、数据范围、描述、状态、权限）
    参数:
        - role_id: 角色ID（整数类型）
        - req: 角色更新信息（所有字段都是可选的）
    返回: 更新成功消息
    返回格式: {"data": {}, "message": "角色信息更新成功", "status": 200}
    特殊处理:
        - 如果提供了权限列表，会先删除旧权限，再添加新权限
        - 权限更新失败不会影响角色信息更新
    注意:
        - 如果角色不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT id FROM roles WHERE id = %s", (role_id,))
        if not cursor.fetchone():
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="角色不存在", status=404)

        # 验证editPermission字段（如果提供）
        if req.editPermission is not None:
            if req.editPermission not in ["enabled", "disabled"]:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="编辑权限值无效，必须为 enabled 或 disabled", status=400)

        update_fields = []
        params = []
        if req.name is not None:
            update_fields.append("name = %s")
            params.append(req.name)
        if req.dataScope is not None:
            update_fields.append("data_scope = %s")
            params.append(req.dataScope)
        if req.description is not None:
            update_fields.append("description = %s")
            params.append(req.description)
        if req.editPermission is not None:
            update_fields.append("edit_permission = %s")
            params.append(req.editPermission)
        if req.status is not None:
            update_fields.append("status = %s")
            params.append(req.status)

        if update_fields:
            params.append(role_id)
            cursor.execute(f"UPDATE roles SET {', '.join(update_fields)} WHERE id = %s", params)

        # 更新权限
        if req.permissions is not None:
            cursor.execute("DELETE FROM role_permissions WHERE role_id = %s", (role_id,))
            for perm_id in req.permissions:
                try:
                    cursor.execute(
                        "INSERT INTO role_permissions (role_id, permission_id) VALUES (%s, %s)",
                        (role_id, perm_id)
                    )
                except Exception:
                    pass

        conn.commit()
        cursor.close()
        conn.close()
        return create_wrapped_response(data={}, message="角色信息更新成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新角色失败: {str(e)}")


@app.delete("/api/roles/{role_id}", response_model=WrappedApiResponse)
async def api_delete_role(role_id: int):
    """
    删除角色接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles
    功能: 根据角色ID删除角色
    参数:
        - role_id: 角色ID（整数类型）
    返回: 删除成功消息
    返回格式: {"data": None, "message": "角色删除成功", "status": 200}
    注意:
        - 如果角色不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        affected = cursor.execute("DELETE FROM roles WHERE id = %s", (role_id,))
        conn.commit()
        cursor.close()
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="角色不存在", status=404)
        return create_wrapped_response(data=None, message="角色删除成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除角色失败: {str(e)}")


# ==================== 组织管理接口 ====================

@app.get("/api/organizations", response_model=WrappedApiResponse)
async def api_get_organizations_list(
        page: int = Query(1, ge=1),
        pageSize: int = Query(None, ge=1, le=100, description="每页数量（驼峰命名，兼容前端）"),
        page_size: int = Query(10, ge=1, le=100, description="每页数量（下划线命名，统一格式）"),
        search: Optional[str] = Query(None),
        region: Optional[str] = Query(None),
        status: Optional[str] = Query(None)
):
    """
    获取组织列表接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: organizations, users
    功能: 获取组织列表，支持分页和多条件筛选（搜索、区域、状态），并统计每个组织的用户数
    参数:
        - page: 页码，默认1，最小值为1
        - pageSize: 每页数量（驼峰命名，兼容前端），默认10，范围1-100
        - page_size: 每页数量（下划线命名，统一格式），默认10，范围1-100
        - search: 搜索关键词，匹配组织名称（可选）
        - region: 区域筛选（可选）
        - status: 状态筛选（可选，支持enabled/disabled格式）
    返回: 分页的组织列表，包含组织基本信息、父级ID、区域、类型、状态、用户数、创建时间、更新时间
    返回格式: {
        "data": {
            "list": [
                {
                    "id": 1,
                    "name": "技术部",
                    "code": "ORG001",
                    "parentId": 0,
                    "region": "广东省/深圳市/南山区",
                    "type": "sales",
                    "status": "enabled",
                    "userCount": 25,
                    "description": "负责技术开发",
                    "createTime": "2024-01-15",
                    "updateTime": "2024-01-15"
                },
                ...
            ],
            "pagination": {
                "page": 1,
                "pageSize": 10,
                "page_size": 10,
                "total": 100,
                "totalPages": 10,
                "total_pages": 10
            }
        }
    }
    字段说明:
        - status: 返回值为 "enabled" 或 "disabled"（数据库存储为 "active" 或 "inactive"，自动转换）
        - userCount: 通过users表的department字段统计组织内用户数
        - region: 区域格式为 "省/市" 或 "省/市/区"
    特殊处理: 如果organizations表不存在，返回空列表而不是错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    # 统一使用page_size参数，如果没有则使用pageSize参数
    actual_page_size = page_size if page_size is not None else (pageSize if pageSize is not None else 10)

    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params = []
        if search:
            where.append("name LIKE %s")
            params.append(f"%{search}%")
        if region:
            # 支持多区域筛选：region字段可能是JSON数组，需要匹配任一区域
            # 使用JSON_SEARCH或LIKE匹配（兼容旧数据格式）
            # JSON_SEARCH用于查找JSON数组中是否包含指定字符串
            where.append("(region LIKE %s OR JSON_SEARCH(region, 'one', %s) IS NOT NULL)")
            params.append(f"%{region}%")
            params.append(f"%{region}%")
        if status:
            # 转换前端传入的status值：enabled -> active, disabled -> inactive
            status_filter = status
            if status == "enabled":
                status_filter = "active"
            elif status == "disabled":
                status_filter = "inactive"
            where.append("status = %s")
            params.append(status_filter)

        where_clause = " AND ".join(where) if where else "1=1"

        try:
            cursor.execute(f"SELECT COUNT(*) AS cnt FROM organizations WHERE {where_clause}", params)
            total = cursor.fetchone()["cnt"]

            cursor.execute(
                f"""
                SELECT id, name, code, parent_id, region, type, status, description, created_at, updated_at
                FROM organizations
                WHERE {where_clause}
                ORDER BY id DESC
                LIMIT %s OFFSET %s
                """,
                params + [actual_page_size, (page - 1) * actual_page_size]
            )
            rows = cursor.fetchall()

            data = []
            for r in rows:
                # 统计组织内用户数（通过department字段关联）
                cursor.execute("SELECT COUNT(*) AS cnt FROM users WHERE department = %s", (r["name"],))
                user_count = cursor.fetchone()["cnt"]

                # 转换status值：active -> enabled, inactive -> disabled
                status_value = r.get("status", "active")
                if status_value == "active":
                    status_value = "enabled"
                elif status_value == "inactive":
                    status_value = "disabled"

                # 处理region字段，解析为数组格式
                region_list = parse_region_from_storage(r.get("region"))

                data.append({
                    "id": r["id"],
                    "name": r["name"],
                    "code": r.get("code", ""),
                    "parentId": r.get("parent_id", 0),
                    "region": region_list,  # 返回数组格式，如 ["北京/北京市/全部", "上海/上海市/全部"]
                    "type": r.get("type", ""),
                    "status": status_value,
                    "userCount": user_count,
                    "description": r.get("description", ""),
                    "createTime": r["created_at"].strftime("%Y-%m-%d") if r["created_at"] else None,
                    "updateTime": r["updated_at"].strftime("%Y-%m-%d") if r["updated_at"] else None
                })
        except Exception as e:
            # 表不存在或查询失败时返回空数据，并记录错误日志
            print(f"查询organizations表失败: {str(e)}")
            total = 0
            data = []

        result = {
            "list": data,
            "pagination": {
                "page": page,
                "pageSize": actual_page_size,  # 使用驼峰命名，兼容前端
                "page_size": actual_page_size,  # 同时保留下划线命名
                "total": total,
                "totalPages": (total + actual_page_size - 1) // actual_page_size if actual_page_size > 0 else 0,
                # 使用驼峰命名
                "total_pages": (total + actual_page_size - 1) // actual_page_size if actual_page_size > 0 else 0
                # 同时保留下划线命名
            }
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=result, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取组织列表失败: {str(e)}")


# ==================== 获取组织类型可选值接口 ====================
# 注意：此接口必须在 /api/organizations/{org_id} 之前定义，避免路由冲突

@app.get("/api/organizations/types", response_model=WrappedApiResponse)
async def api_get_organization_types():
    """
    获取组织类型可选值接口
    文档来源: 后端接口修改需求文档.md
    操作表: 无（返回预定义数据）
    功能: 获取组织类型的可选值列表，用于前端下拉选择
    参数: 无
    返回: 组织类型列表
    返回格式: {
        "code": 200,
        "message": "success",
        "data": [
            {"value": "sales", "label": "销售部门"},
            {"value": "support", "label": "支持部门"},
            {"value": "admin", "label": "管理部门"},
            {"value": "warehouse", "label": "仓库部门"}
        ]
    }
    """
    types = [
        {"value": "sales", "label": "销售部门"},
        {"value": "support", "label": "支持部门"},
        {"value": "admin", "label": "管理部门"},
        {"value": "warehouse", "label": "仓库部门"}
    ]
    # 直接返回数组，不要嵌套在data.data中
    return WrappedApiResponse(
        data=types,  # 直接返回数组，不是字典
        message="success",
        status=200
    )


@app.post("/api/organizations", response_model=WrappedApiResponse)
async def api_create_organization(req: OrganizationCreate):
    """
    创建组织接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: organizations
    功能: 创建新组织
    参数:
        - req: 组织创建信息（名称、代码（可选）、父级ID、区域、类型、描述、状态）
    返回: 创建成功的组织ID
    返回格式: {"data": {"id": 1}, "message": "组织创建成功", "status": 200}
    注意:
        - code字段如果前端不传，后端会自动生成（格式: ORG001, ORG002...）
        - region字段接受格式: "广东省/深圳市" 或 "广东省/深圳市/南山区"
        - type字段可选值: sales(销售部门), support(支持部门), admin(管理部门), warehouse(仓库部门)
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)  # 使用DictCursor以返回字典格式

        # 如果前端没有提供code，自动生成
        code = req.code
        if not code:
            # 查询所有ORG开头的code，找到最大的数字，避免重复
            cursor.execute("SELECT code FROM organizations WHERE code LIKE 'ORG%'")
            all_codes = cursor.fetchall()

            if all_codes:
                max_num = 0
                for row in all_codes:
                    code_str = row.get('code', '')
                    if code_str and code_str.startswith('ORG'):
                        try:
                            # 提取数字部分（ORG001 -> 001 -> 1）
                            num_str = code_str[3:]  # 移除'ORG'前缀
                            num = int(num_str)
                            max_num = max(max_num, num)
                        except (ValueError, IndexError):
                            # 如果转换失败，跳过这个code
                            pass
                # 生成下一个code
                code = f"ORG{max_num + 1:03d}"
            else:
                # 如果没有找到任何ORG开头的code，从1开始
                code = "ORG001"

        # 处理region字段：支持字符串或数组格式，统一转换为JSON字符串存储
        region_storage = format_region_for_storage(req.region)
        # 验证至少有一个区域
        region_list = normalize_region_to_list(req.region)
        if not region_list:
            cursor.close()
            conn.close()
            raise HTTPException(status_code=400, detail="至少需要配置一个区域")
        
        cursor.execute(
            """
            INSERT INTO organizations (name, code, parent_id, region, type, description, status)
            VALUES (%s, %s, %s, %s, %s, %s, %s)
            """,
            (req.name, code, req.parentId, region_storage, req.type, req.description, req.status)
        )
        org_id = cursor.lastrowid
        conn.commit()
        cursor.close()
        conn.close()

        # 直接返回数据，不要嵌套在data.data中
        return WrappedApiResponse(
            data={"id": org_id, "code": code},
            message="组织创建成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"创建组织失败: {str(e)}")


@app.get("/api/organizations/{org_id}", response_model=WrappedApiResponse)
async def api_get_organization_detail(org_id: int):
    """
    获取组织详情接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: organizations, users
    功能: 根据组织ID获取组织详细信息，包括组织内的用户数
    参数:
        - org_id: 组织ID（整数类型）
    返回: 组织详细信息，包含基本信息、父级ID、区域、类型、状态、用户数、创建时间、更新时间
    返回格式: {
        "data": {
            "id": 1,
            "name": "技术部",
            "code": "TECH",
            "parentId": 0,
            "region": "北京市",
            "type": "部门",
            "status": "active",
            "userCount": 10,
            "description": "技术研发部门",
            "createTime": "2024-01-01",
            "updateTime": "2024-01-01"
        }
    }
    特殊处理: 如果组织不存在或organizations表不存在，返回404错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        try:
            cursor.execute(
                "SELECT id, name, code, parent_id, region, type, status, description, created_at, updated_at FROM organizations WHERE id = %s",
                (org_id,)
            )
            r = cursor.fetchone()

            if not r:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="组织不存在", status=404)

            cursor.execute("SELECT COUNT(*) AS cnt FROM users WHERE department = %s", (r["name"],))
            user_count = cursor.fetchone()["cnt"]

            # 处理region字段，解析为数组格式
            region_list = parse_region_from_storage(r.get("region"))
            
            data = {
                "id": r["id"],
                "name": r["name"],
                "code": r.get("code", ""),
                "parentId": r.get("parent_id", 0),
                "region": region_list,  # 返回数组格式，如 ["北京/北京市/全部", "上海/上海市/全部"]
                "type": r.get("type", ""),
                "status": r.get("status", "active"),
                "userCount": user_count,
                "description": r.get("description"),
                "createTime": r["created_at"].strftime("%Y-%m-%d") if r["created_at"] else None,
                "updateTime": r["updated_at"].strftime("%Y-%m-%d") if r["updated_at"] else None
            }
        except Exception:
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="组织不存在", status=404)

        cursor.close()
        conn.close()
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取组织详情失败: {str(e)}")


@app.put("/api/organizations/{org_id}", response_model=WrappedApiResponse)
async def api_update_organization(org_id: int, req: OrganizationUpdate):
    """
    更新组织接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: organizations
    功能: 更新组织信息（名称、代码、父级ID、区域、类型、描述、状态）
    参数:
        - org_id: 组织ID（整数类型）
        - req: 组织更新信息（所有字段都是可选的）
    返回: 更新成功消息
    返回格式: {"data": {}, "message": "组织信息更新成功", "status": 200}
    注意:
        - 如果组织不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT id FROM organizations WHERE id = %s", (org_id,))
        if not cursor.fetchone():
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="组织不存在", status=404)

        update_fields = []
        params = []
        if req.name is not None:
            update_fields.append("name = %s")
            params.append(req.name)
        if req.code is not None:
            update_fields.append("code = %s")
            params.append(req.code)
        if req.parentId is not None:
            update_fields.append("parent_id = %s")
            params.append(req.parentId)
        if req.region is not None:
            # 处理region字段：支持字符串或数组格式，统一转换为JSON字符串存储
            region_storage = format_region_for_storage(req.region)
            # 验证至少有一个区域
            region_list = normalize_region_to_list(req.region)
            if not region_list:
                cursor.close()
                conn.close()
                raise HTTPException(status_code=400, detail="至少需要配置一个区域")
            update_fields.append("region = %s")
            params.append(region_storage)
        if req.type is not None:
            update_fields.append("type = %s")
            params.append(req.type)
        if req.description is not None:
            update_fields.append("description = %s")
            params.append(req.description)
        if req.status is not None:
            update_fields.append("status = %s")
            params.append(req.status)

        if update_fields:
            params.append(org_id)
            cursor.execute(f"UPDATE organizations SET {', '.join(update_fields)} WHERE id = %s", params)
            conn.commit()

        cursor.close()
        conn.close()
        return create_wrapped_response(data={}, message="组织信息更新成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新组织失败: {str(e)}")


@app.delete("/api/organizations/{org_id}", response_model=WrappedApiResponse)
async def api_delete_organization(org_id: int):
    """
    删除组织接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: organizations
    功能: 根据组织ID删除组织
    参数:
        - org_id: 组织ID（整数类型）
    返回: 删除成功消息
    返回格式: {"data": None, "message": "组织删除成功", "status": 200}
    注意:
        - 如果组织不存在，返回404错误
        - 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        affected = cursor.execute("DELETE FROM organizations WHERE id = %s", (org_id,))
        conn.commit()
        cursor.close()
        conn.close()
        if affected == 0:
            return create_wrapped_response(data=None, message="组织不存在", status=404)
        return create_wrapped_response(data=None, message="组织删除成功", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除组织失败: {str(e)}")


# ==================== 权限管理接口 ====================

@app.get("/api/permissions", response_model=WrappedApiResponse)
async def api_get_permissions(
        type: Optional[str] = Query(None),
        status: Optional[str] = Query(None),
        tree: bool = Query(False)
):
    """
    获取权限列表接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: permissions
    功能: 获取权限列表，支持按类型和状态筛选，可选择返回树形结构
    参数:
        - type: 权限类型筛选（可选）
        - status: 状态筛选（可选）
        - tree: 是否返回树形结构，默认false（可选）
    返回: 权限列表（列表或树形结构）
    返回格式（列表）: [
        {
            "id": 1,
            "name": "用户管理",
            "code": "user:manage",
            "type": "menu",
            "parentId": 0,
            "path": "/users",
            "icon": "user",
            "orderNumber": 1,
            "status": "active",
            "createTime": "2024-01-01",
            "updateTime": "2024-01-01"
        },
        ...
    ]
    返回格式（树形）: [
        {
            "id": 1,
            "name": "用户管理",
            "children": [...]
        },
        ...
    ]
    特殊处理: 如果permissions表不存在，返回空列表而不是错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        where = []
        params = []
        if type:
            where.append("type = %s")
            params.append(type)
        if status:
            where.append("status = %s")
            params.append(status)

        where_clause = " AND ".join(where) if where else "1=1"

        try:
            cursor.execute(
                f"""
                SELECT id, name, code, type, parent_id, path, icon, order_number, status, created_at, updated_at
                FROM permissions
                WHERE {where_clause}
                ORDER BY order_number ASC, id ASC
                """
            )
            rows = cursor.fetchall()

            permissions = []
            for r in rows:
                permissions.append({
                    "id": r["id"],
                    "name": r["name"],
                    "code": r["code"],
                    "type": r["type"],
                    "parentId": r.get("parent_id", 0),
                    "path": r.get("path"),
                    "icon": r.get("icon"),
                    "orderNumber": r.get("order_number", 0),
                    "status": r.get("status", "active"),
                    "createTime": r["created_at"].strftime("%Y-%m-%d") if r["created_at"] else None,
                    "updateTime": r["updated_at"].strftime("%Y-%m-%d") if r["updated_at"] else None
                })

            # 如果tree=true，构建树形结构
            if tree:
                perm_map = {p["id"]: p for p in permissions}
                tree_data = []
                for p in permissions:
                    if p["parentId"] == 0:
                        tree_data.append(p)
                    else:
                        parent = perm_map.get(p["parentId"])
                        if parent:
                            if "children" not in parent:
                                parent["children"] = []
                            parent["children"].append(p)
                permissions = tree_data
        except Exception:
            permissions = []

        cursor.close()
        conn.close()
        return create_wrapped_response(data=permissions, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取权限列表失败: {str(e)}")


@app.get("/api/roles/{role_id}/permissions", response_model=WrappedApiResponse)
async def api_get_role_permissions(role_id: int):
    """
    获取角色权限接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, role_permissions, permissions
    功能: 获取指定角色的所有权限列表
    参数:
        - role_id: 角色ID（整数类型）
    返回: 角色权限信息，包含角色ID、角色名称和权限列表（每个权限标记为已选中）
    返回格式: {
        "data": {
            "roleId": 1,
            "roleName": "管理员",
            "permissions": [
                {"id": 1, "name": "用户管理", "code": "user:manage", "type": "menu", "checked": true},
                ...
            ]
        }
    }
    特殊处理: 如果角色不存在或相关表不存在，返回404错误或空列表
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        try:
            cursor.execute("SELECT name FROM roles WHERE id = %s", (role_id,))
            role = cursor.fetchone()
            if not role:
                cursor.close()
                conn.close()
                return create_wrapped_response(data=None, message="角色不存在", status=404)

            cursor.execute(
                "SELECT permission_id FROM role_permissions WHERE role_id = %s",
                (role_id,)
            )
            perm_rows = cursor.fetchall()

            permissions = []
            for pr in perm_rows:
                cursor.execute("SELECT id, name, code, type FROM permissions WHERE id = %s", (pr["permission_id"],))
                p = cursor.fetchone()
                if p:
                    permissions.append({
                        "id": p["id"],
                        "name": p["name"],
                        "code": p["code"],
                        "type": p["type"],
                        "checked": True
                    })
        except Exception:
            role = None
            permissions = []

        if not role:
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="角色不存在", status=404)

        data = {
            "roleId": role_id,
            "roleName": role["name"],
            "permissions": permissions
        }

        cursor.close()
        conn.close()
        return create_wrapped_response(data=data, message="success", status=200)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取角色权限失败: {str(e)}")


@app.post("/api/roles/{role_id}/permissions", response_model=WrappedApiResponse)
async def api_set_role_permissions(role_id: int, req: RolePermissionsRequest):
    """
    设置角色权限接口
    文档来源: 用户管理和权限管理API文档.md
    操作表: roles, role_permissions
    功能: 为指定角色设置权限（先删除旧权限，再添加新权限）
    参数:
        - role_id: 角色ID（整数类型）
        - req: 权限ID列表（permissionIds字段）
    返回: 设置成功消息和权限数量
    返回格式: {
        "data": {"roleId": 1, "permissionCount": 10},
        "message": "角色权限设置成功",
        "status": 200
    }
    特殊处理:
        - 权限添加失败不会影响其他权限的添加
        - 如果角色不存在，返回404错误
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute("SELECT id FROM roles WHERE id = %s", (role_id,))
        if not cursor.fetchone():
            cursor.close()
            conn.close()
            return create_wrapped_response(data=None, message="角色不存在", status=404)

        # 删除旧权限
        cursor.execute("DELETE FROM role_permissions WHERE role_id = %s", (role_id,))

        # 添加新权限
        for perm_id in req.permissionIds:
            try:
                cursor.execute(
                    "INSERT INTO role_permissions (role_id, permission_id) VALUES (%s, %s)",
                    (role_id, perm_id)
                )
            except Exception:
                pass

        conn.commit()
        cursor.close()
        conn.close()

        return create_wrapped_response(
            data={"roleId": role_id, "permissionCount": len(req.permissionIds)},
            message="角色权限设置成功",
            status=200
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"设置角色权限失败: {str(e)}")


# ==================== 获取人员日程安排接口（无鉴权、无userid） ====================

@app.get("/api/user-visit-records/{user_id}")
async def api_get_user_visit_records(user_id: int, minDate: str = Query(...), maxDate: str = Query(...)):
    """
    获取人员日程安排接口（日历用）
    文档来源: 新增接口和字段说明.md
    操作表: user_visit_records
    功能: 获取指定用户在指定日期范围内的日程安排，用于日历展示
    参数:
        - user_id: 用户ID（整数类型）
        - minDate: 最小日期，格式为"YYYY-MM-DD"（必填）
        - maxDate: 最大日期，格式为"YYYY-MM-DD"（必填）
    返回: 日程安排列表，每个条目包含日期和到访信息
    返回格式: {
        "data": [
            {"date": "2025-11-01", "info": "到访"},
            {"date": "2025-11-02", "info": "到访"},
            ...
        ]
    }
    特殊处理:
        - info字段必须不为空，默认为"到访"
        - 如果出错，返回空数组而不是错误
        - 直接返回data数组，不包装在双层data中
    注意: 此接口不需要鉴权，用于桌面应用
    """
    try:
        conn = get_db_connection()
        cursor = conn.cursor(_DictCursorAlias)

        # 查询user_visit_records表中的日程日期
        cursor.execute("""
            SELECT DISTINCT schedule_date 
            FROM user_visit_records 
            WHERE user_id = %s AND schedule_date BETWEEN %s AND %s
            ORDER BY schedule_date
        """, (user_id, minDate, maxDate))

        records = cursor.fetchall()

        # 构造返回数据，每个日期都有info字段且不为空
        data = []
        for record in records:
            schedule_date = record['schedule_date']
            if isinstance(schedule_date, str):
                date_str = schedule_date
            else:
                # 如果是date对象，转换为字符串
                date_str = schedule_date.strftime('%Y-%m-%d') if schedule_date else None

            if date_str:
                data.append({
                    "date": date_str,
                    "info": "到访"  # info字段不能为空，默认为"到访"
                })

        cursor.close()
        conn.close()

        # 直接返回data数组，不包装在双层data中
        return {"data": data}
    except Exception as e:
        # 出错时返回空数组
        return {"data": []}


# ==================== 小程序需要key ====================
# -------------------- 工具函数 --------------------
def hmacsha256(key, data):
    """
    计算HMAC-SHA256哈希值的函数

    :param key: 用于计算哈希的密钥，字节类型
    :param data: 要进行哈希计算的数据，字符串类型
    :return: 计算得到的HMAC-SHA256哈希值，字节类型
    """
    try:
        mac = hmac.new(key, data.encode(), hashlib.sha256)
        hmacBytes = mac.digest()
        return hmacBytes
    except Exception as e:
        raise RuntimeError(f"Failed to calculate HMAC-SHA256 due to {e}")




# ==================== OSS签名接口 ====================阿里云上传文件接口,不能动
@app.get("/generate_signature")
async def generate_signature():
    """
    处理生成签名信息的请求，执行相关逻辑流程，包括获取环境变量、创建策略、构造待签名字符串、计算签名等操作，
    并返回生成的签名信息。
    :return: JSON格式的响应，包含签名信息的字典，格式如下：
    {
        "policy": "policy字符串",
        "x-oss-signature-version": "OSS4-HMAC-SHA256",
        "x-oss-credential": "accesskeyid/日期/cn-beijing/oss/aliyun_v4_request",
        "x-oss-date": "请求时间",
        "signature": "签名",
        "security_token": "安全令牌"
    }
    """
    access_key_id = 'LTAI5tPC3BHcGXKR5P9XJtnU'
    access_key_secret = 'OlQG2FR0UA0mDfdSb4dHwxPlJy1l6P'
    # # 下面这个是主账户的
    # access_key_id = 'LTAI5tNtgEjQiVNRAXRbwzQK'
    # access_key_secret = 'EcJ7lpXAc6tOPcPoqeWQJfj1VzEsZo'
    role_arn_for_oss_upload = 'acs:ram::1910367374586911:role/qianrenqiandianoss'

    # 自定义会话名称
    role_session_name = 'QIANRENQIANDIANoss'

    # 指定过期时间，单位为秒
    expire_time = 3600

    bucket = 'qrqd'
    region_id = 'cn-beijing'

    try:
        # 初始化配置，直接传递凭据
        # TeaConfig 的参数可能需要使用不同的方式传递
        config = TeaConfig()
        config.region_id = region_id
        config.access_key_id = access_key_id
        config.access_key_secret = access_key_secret

        # 创建 STS 客户端并获取临时凭证
        sts_client = Sts20150401Client(config=config)
        assume_role_request = sts_20150401_models.AssumeRoleRequest(
            role_arn=role_arn_for_oss_upload,
            role_session_name=role_session_name
        )
        response = sts_client.assume_role(assume_role_request)
        token_data = response.body.credentials.to_map()
    except Exception as e:
        error_detail = f"创建STS客户端或获取临时凭证失败: {str(e)}\n{traceback.format_exc()}"
        print(f"[ERROR] {error_detail}")
        raise HTTPException(status_code=500, detail=error_detail)

    # 使用 STS 返回的临时凭据
    sts_accesskeyid = token_data['AccessKeyId']
    sts_accesskeysecret = token_data['AccessKeySecret']
    security_token = token_data['SecurityToken']

    now = int(time.time())

    # 将时间戳转换为datetime对象
    dt_obj = datetime.utcfromtimestamp(now)
    # 在当前时间增加3小时，设置为请求的过期时间
    dt_obj_plus_3h = dt_obj + timedelta(hours=3)

    # 请求时间
    dt_obj_1 = dt_obj.strftime('%Y%m%dT%H%M%S') + 'Z'
    # 请求日期
    dt_obj_2 = dt_obj.strftime('%Y%m%d')
    # 请求过期时间
    expiration_time = dt_obj_plus_3h.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    # 步骤1：创建policy。
    # 示例policy表单域只列举必填字段，如有其他需求可参考签名版本4文档：https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend
    policy = {
        "expiration": expiration_time,
        "conditions": [
            {"bucket": "qrqd"},   #请将<bucket_name>替换为您的实际Bucket名称
            {"x-oss-signature-version": "OSS4-HMAC-SHA256"},
            {"x-oss-credential": f"{sts_accesskeyid}/{dt_obj_2}/cn-beijing/oss/aliyun_v4_request"},    #请将<cn-beijing>替换为您的实际Bucket所处地域，例如北京地域为：cn-beijing
            {"x-oss-security-token": security_token},
            {"x-oss-date": dt_obj_1},
        ]
    }
    policy_str = json.dumps(policy).strip()

    # 步骤2：构造待签名字符串（StringToSign）
    stringToSign = base64.b64encode(policy_str.encode()).decode()

    # 步骤3：计算SigningKey
    dateKey = hmacsha256(("aliyun_v4" + sts_accesskeysecret).encode(), dt_obj_2)
    dateRegionKey = hmacsha256(dateKey, "cn-beijing")    #请将<cn-beijing>替换为您的实际Bucket所处地域，例如北京地域为：cn-beijing
    dateRegionServiceKey = hmacsha256(dateRegionKey, "oss")
    signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request")

    # 步骤4：计算Signature
    result = hmacsha256(signingKey, stringToSign)
    signature = result.hex()

    return {
        "policy": stringToSign,
        "x_oss_signature_version": "OSS4-HMAC-SHA256",
        "x_oss_credential": f"{sts_accesskeyid}/{dt_obj_2}/cn-beijing/oss/aliyun_v4_request",    #请将<cn-beijing>替换为您的实际Bucket所处地域，例如北京地域为：cn-beijing
        "x_oss_date": dt_obj_1,
        "signature": signature,
        "security_token": security_token
    }

# ==================== 应用启动 ====================

if __name__ == "__main__":
    """
    启动FastAPI应用
    主机: 0.0.0.0 (允许外部访问)
    端口: 8018
    数据库: 阿里云RDS MySQL (qrqd)
    """
    # https://aimk.chinacpt.com:8018
    uvicorn.run(app, host="0.0.0.0", port=8018)
    # uvicorn.run(app, host="0.0.0.0", port=8018, ssl_keyfile="nginx.key", ssl_certfile="nginx.crt")
